/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2011 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Ban processing
 *
 * A ban consists of a number of conditions (or tests), all of which must be
 * satisfied.  Here are some potential bans we could support:
 *
 *	req.url == "/foo"
 *	req.url ~ ".iso" && obj.size > 10MB
 *	req.http.host ~ "web1.com" && obj.set-cookie ~ "USER=29293"
 *
 * We make the "&&" mandatory from the start, leaving the syntax space
 * for latter handling of "||" as well.
 *
 */

#include "config.h"

#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <pcre.h>

#include "vcli.h"
#include "vend.h"
#include "cli_priv.h"
#include "cache.h"
#include "hash_slinger.h"

struct ban {
	unsigned		magic;
#define BAN_MAGIC		0x700b08ea
	VTAILQ_ENTRY(ban)	list;
	unsigned		refcount;
	unsigned		flags;
#define BAN_F_GONE		(1 << 0)
#define BAN_F_REQ		(1 << 2)
#define BAN_F_LURK		(3 << 6)	/* ban-lurker-color */
	VTAILQ_HEAD(,objcore)	objcore;
	struct vsb		*vsb;
	uint8_t			*spec;
};

#define LURK_SHIFT 6

struct ban_test {
	uint8_t			arg1;
	const char		*arg1_spec;
	uint8_t			oper;
	const char		*arg2;
	const void		*arg2_spec;
};

static VTAILQ_HEAD(banhead_s,ban) ban_head = VTAILQ_HEAD_INITIALIZER(ban_head);
static struct lock ban_mtx;
static struct ban *ban_magic;
static pthread_t ban_thread;
static struct ban * volatile ban_start;
static bgthread_t ban_lurker;

/*--------------------------------------------------------------------
 * BAN string magic markers
 */

#define	BAN_OPER_EQ	0x10
#define	BAN_OPER_NEQ	0x11
#define	BAN_OPER_MATCH	0x12
#define	BAN_OPER_NMATCH	0x13

#define BAN_ARG_URL	0x18
#define BAN_ARG_REQHTTP	0x19
#define BAN_ARG_OBJHTTP	0x1a

/*--------------------------------------------------------------------
 * Variables we can purge on
 */

static const struct pvar {
	const char		*name;
	unsigned		flag;
	uint8_t			tag;
} pvars[] = {
#define PVAR(a, b, c)	{ (a), (b), (c) },
#include "ban_vars.h"
#undef PVAR
	{ 0, 0, 0}
};

/*--------------------------------------------------------------------
 * Storage handling of bans
 */

struct ban *
BAN_New(void)
{
	struct ban *b;

	ALLOC_OBJ(b, BAN_MAGIC);
	if (b == NULL)
		return (b);
	b->vsb = VSB_new_auto();
	if (b->vsb == NULL) {
		FREE_OBJ(b);
		return (NULL);
	}
	VTAILQ_INIT(&b->objcore);
	return (b);
}

void
BAN_Free(struct ban *b)
{

	CHECK_OBJ_NOTNULL(b, BAN_MAGIC);
	AZ(b->refcount);
	assert(VTAILQ_EMPTY(&b->objcore));

	if (b->vsb != NULL)
		VSB_delete(b->vsb);
	if (b->spec != NULL)
		free(b->spec);
	FREE_OBJ(b);
}

/*--------------------------------------------------------------------
 * Get & Release a tail reference, used to hold the list stable for
 * traversals etc.
 */

struct ban *
BAN_TailRef(void)
{
	struct ban *b;

	ASSERT_CLI();
	Lck_Lock(&ban_mtx);
	b = VTAILQ_LAST(&ban_head, banhead_s);
	AN(b);
	b->refcount++;
	Lck_Unlock(&ban_mtx);
	return (b);
}

void
BAN_TailDeref(struct ban **bb)
{
	struct ban *b;

	b = *bb;
	*bb = NULL;
	Lck_Lock(&ban_mtx);
	b->refcount--;
	Lck_Unlock(&ban_mtx);
}

/*--------------------------------------------------------------------
 * Extract time and length from ban-spec
 */

static double
ban_time(const uint8_t *banspec)
{
	double t;

	assert(sizeof t == 8);
	memcpy(&t, banspec, sizeof t);
	return (t);
}

static unsigned
ban_len(const uint8_t *banspec)
{
	unsigned u;

	u = vbe32dec(banspec + 8);
	return (u);
}

/*--------------------------------------------------------------------
 * Access a lump of bytes in a ban test spec
 */

static void
ban_add_lump(const struct ban *b, const void *p, uint32_t len)
{
	uint8_t buf[sizeof len];

	vbe32enc(buf, len);
	VSB_bcat(b->vsb, buf, sizeof buf);
	VSB_bcat(b->vsb, p, len);
}

static const void *
ban_get_lump(const uint8_t **bs)
{
	const void *r;
	unsigned ln;

	ln = vbe32dec(*bs);
	*bs += 4;
	r = (const void*)*bs;
	*bs += ln;
	return (r);
}

/*--------------------------------------------------------------------
 * Pick a test apart from a spec string
 */

static void
ban_iter(const uint8_t **bs, struct ban_test *bt)
{

	memset(bt, 0, sizeof *bt);
	bt->arg1 = *(*bs)++;
	if (bt->arg1 == BAN_ARG_REQHTTP || bt->arg1 == BAN_ARG_OBJHTTP) {
		bt->arg1_spec = (const char *)*bs;
		(*bs) += (*bs)[0] + 2;
	}
	bt->arg2 = ban_get_lump(bs);
	bt->oper = *(*bs)++;
	if (bt->oper == BAN_OPER_MATCH || bt->oper == BAN_OPER_NMATCH)
		bt->arg2_spec = ban_get_lump(bs);
}

/*--------------------------------------------------------------------
 * Parse and add a http argument specification
 * Output something which HTTP_GetHdr understands
 */

static void
ban_parse_http(const struct ban *b, const char *a1)
{
	int l;

	l = strlen(a1) + 1;
	assert(l <= 127);
	VSB_putc(b->vsb, (char)l);
	VSB_cat(b->vsb, a1);
	VSB_putc(b->vsb, ':');
	VSB_putc(b->vsb, '\0');
}

/*--------------------------------------------------------------------
 * Parse and add a ban test specification
 */

static int
ban_parse_regexp(struct cli *cli, const struct ban *b, const char *a3)
{
	const char *error;
	int erroroffset, rc;
	size_t sz;
	pcre *re;

	re = pcre_compile(a3, 0, &error, &erroroffset, NULL);
	if (re == NULL) {
		VSL(SLT_Debug, 0, "REGEX: <%s>", error);
		VCLI_Out(cli, "%s", error);
		VCLI_SetResult(cli, CLIS_PARAM);
		return (-1);
	}
	rc = pcre_fullinfo(re, NULL, PCRE_INFO_SIZE, &sz);
	AZ(rc);
	ban_add_lump(b, re, sz);
	return (0);
}

/*--------------------------------------------------------------------
 * Add a (and'ed) test-condition to a ban
 */

int
BAN_AddTest(struct cli *cli, struct ban *b, const char *a1, const char *a2,
    const char *a3)
{
	const struct pvar *pv;
	int i;

	CHECK_OBJ_NOTNULL(b, BAN_MAGIC);

	for (pv = pvars; pv->name != NULL; pv++)
		if (!strncmp(a1, pv->name, strlen(pv->name)))
			break;
	if (pv->name == NULL) {
		VCLI_Out(cli, "unknown or unsupported field \"%s\"", a1);
		VCLI_SetResult(cli, CLIS_PARAM);
		return (-1);
	}

	if (pv->flag & PVAR_REQ)
		b->flags |= BAN_F_REQ;

	VSB_putc(b->vsb, pv->tag);
	if (pv->flag & PVAR_HTTP)
		ban_parse_http(b, a1 + strlen(pv->name));

	ban_add_lump(b, a3, strlen(a3) + 1);
	if (!strcmp(a2, "~")) {
		VSB_putc(b->vsb, BAN_OPER_MATCH);
		i = ban_parse_regexp(cli, b, a3);
		if (i)
			return (i);
	} else if (!strcmp(a2, "!~")) {
		VSB_putc(b->vsb, BAN_OPER_NMATCH);
		i = ban_parse_regexp(cli, b, a3);
		if (i)
			return (i);
	} else if (!strcmp(a2, "==")) {
		VSB_putc(b->vsb, BAN_OPER_EQ);
	} else if (!strcmp(a2, "!=")) {
		VSB_putc(b->vsb, BAN_OPER_NEQ);
	} else {
		VCLI_Out(cli,
		    "expected conditional (~, !~, == or !=) got \"%s\"", a2);
		VCLI_SetResult(cli, CLIS_PARAM);
		return (-1);
	}
	return (0);
}

/*--------------------------------------------------------------------
 * We maintain ban_start as a pointer to the first element of the list
 * as a separate variable from the VTAILQ, to avoid depending on the
 * internals of the VTAILQ macros.  We tacitly assume that a pointer
 * write is always atomic in doing so.
 */

void
BAN_Insert(struct ban *b)
{
	struct ban  *bi, *be;
	unsigned pcount;
	ssize_t ln;
	double t0;

	CHECK_OBJ_NOTNULL(b, BAN_MAGIC);

	AZ(VSB_finish(b->vsb));
	ln = VSB_len(b->vsb);
	assert(ln >= 0);

	b->spec = malloc(ln + 13L);	/* XXX */
	XXXAN(b->spec);

	t0 = TIM_real();
	memcpy(b->spec, &t0, sizeof t0);
	b->spec[12] = (b->flags & BAN_F_REQ) ? 1 : 0;
	memcpy(b->spec + 13, VSB_data(b->vsb), ln);
	ln += 13;
	vbe32enc(b->spec + 8, ln);

	VSB_delete(b->vsb);
	b->vsb = NULL;

	Lck_Lock(&ban_mtx);
	VTAILQ_INSERT_HEAD(&ban_head, b, list);
	ban_start = b;
	VSC_C_main->n_ban++;
	VSC_C_main->n_ban_add++;

	be = VTAILQ_LAST(&ban_head, banhead_s);
	if (params->ban_dups && be != b)
		be->refcount++;
	else
		be = NULL;

	SMP_NewBan(b->spec, ln);
	Lck_Unlock(&ban_mtx);

	if (be == NULL)
		return;

	/* Hunt down duplicates, and mark them as gone */
	bi = b;
	pcount = 0;
	Lck_Lock(&ban_mtx);
	while(bi != be) {
		bi = VTAILQ_NEXT(bi, list);
		if (bi->flags & BAN_F_GONE)
			continue;
		/* Safe because the length is part of the fixed size hdr */
		if (memcmp(b->spec + 8, bi->spec + 8, ln - 8))
			continue;
		bi->flags |= BAN_F_GONE;
		VSC_C_main->n_ban_gone++;
		pcount++;
	}
	be->refcount--;
	VSC_C_main->n_ban_dups += pcount;
	Lck_Unlock(&ban_mtx);
}

/*--------------------------------------------------------------------
 * A new object is created, grab a reference to the newest ban
 */

void
BAN_NewObjCore(struct objcore *oc)
{

	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	AZ(oc->ban);
	Lck_Lock(&ban_mtx);
	oc->ban = ban_start;
	ban_start->refcount++;
	VTAILQ_INSERT_TAIL(&ban_start->objcore, oc, ban_list);
	Lck_Unlock(&ban_mtx);
}

/*--------------------------------------------------------------------
 * An object is destroyed, release its ban reference
 */

void
BAN_DestroyObj(struct objcore *oc)
{

	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	if (oc->ban == NULL)
		return;
	CHECK_OBJ_NOTNULL(oc->ban, BAN_MAGIC);
	Lck_Lock(&ban_mtx);
	assert(oc->ban->refcount > 0);
	oc->ban->refcount--;
	VTAILQ_REMOVE(&oc->ban->objcore, oc, ban_list);
	oc->ban = NULL;
	Lck_Unlock(&ban_mtx);
}

/*--------------------------------------------------------------------
 * Find and/or Grab a reference to an objects ban based on timestamp
 * Assume we hold a TailRef, so list traversal is safe.
 */

struct ban *
BAN_RefBan(struct objcore *oc, double t0, const struct ban *tail)
{
	struct ban *b;
	double t1 = 0;

	VTAILQ_FOREACH(b, &ban_head, list) {
		t1 = ban_time(b->spec);
		if (t1 <= t0)
			break;
		if (b == tail)
			break;
	}
	AN(b);
	assert(t1 == t0);
	Lck_Lock(&ban_mtx);
	b->refcount++;
	VTAILQ_INSERT_TAIL(&b->objcore, oc, ban_list);
	Lck_Unlock(&ban_mtx);
	return (b);
}

/*--------------------------------------------------------------------
 * Put a skeleton ban in the list, unless there is an identical,
 * time & condition, ban already in place.
 *
 * If a newer ban has same condition, mark the new ban GONE.
 * mark any older bans, with the same condition, GONE as well.
 */

void
BAN_Reload(const uint8_t *ban, unsigned len)
{
	struct ban *b, *b2;
	int gone = 0;
	double t0, t1, t2 = 9e99;

	ASSERT_CLI();

	t0 = ban_time(ban);
	assert(len == ban_len(ban));
	VTAILQ_FOREACH(b, &ban_head, list) {
		t1 = ban_time(b->spec);
		assert (t1 < t2);
		t2 = t1;
		if (t1 == t0)
			return;
		if (t1 < t0)
			break;
		if (!memcmp(b->spec + 8, ban + 8, len - 8))
			gone |= BAN_F_GONE;
	}

	VSC_C_main->n_ban++;
	VSC_C_main->n_ban_add++;

	b2 = BAN_New();
	AN(b2);
	b2->spec = malloc(len);
	AN(b2->spec);
	memcpy(b2->spec, ban, len);
	b2->flags |= gone;
	if (ban[12])
		b2->flags |= BAN_F_REQ;
	if (b == NULL)
		VTAILQ_INSERT_TAIL(&ban_head, b2, list);
	else
		VTAILQ_INSERT_BEFORE(b, b2, list);

	/* Hunt down older duplicates */
	for (b = VTAILQ_NEXT(b2, list); b != NULL; b = VTAILQ_NEXT(b, list)) {
		if (b->flags & BAN_F_GONE)
			continue;
		if (!memcmp(b->spec + 8, ban + 8, len - 8))
			b->flags |= BAN_F_GONE;
	}
}

/*--------------------------------------------------------------------
 * Get a bans timestamp
 */

double
BAN_Time(const struct ban *b)
{

	if (b == NULL)
		return (0.0);

	CHECK_OBJ_NOTNULL(b, BAN_MAGIC);
	return (ban_time(b->spec));
}

/*--------------------------------------------------------------------
 * All silos have read their bans, ready for action
 */

void
BAN_Compile(void)
{

	ASSERT_CLI();

	SMP_NewBan(ban_magic->spec, ban_len(ban_magic->spec));
	ban_start = VTAILQ_FIRST(&ban_head);
	WRK_BgThread(&ban_thread, "ban-lurker", ban_lurker, NULL);
}

/*--------------------------------------------------------------------
 * Evaluate ban-spec
 */

static int
ban_evaluate(const uint8_t *bs, const struct http *objhttp,
    const struct http *reqhttp, unsigned *tests)
{
	struct ban_test bt;
	const uint8_t *be;
	char *arg1;

	be = bs + ban_len(bs);
	bs += 13;
	while (bs < be) {
		(*tests)++;
		ban_iter(&bs, &bt);
		arg1 = NULL;
		switch (bt.arg1) {
		case BAN_ARG_URL:
			arg1 = reqhttp->hd[HTTP_HDR_URL].b;
			break;
		case BAN_ARG_REQHTTP:
			(void)http_GetHdr(reqhttp, bt.arg1_spec, &arg1);
			break;
		case BAN_ARG_OBJHTTP:
			(void)http_GetHdr(objhttp, bt.arg1_spec, &arg1);
			break;
		default:
			INCOMPL();
		}

		switch (bt.oper) {
		case BAN_OPER_EQ:
			if (arg1 == NULL || strcmp(arg1, bt.arg2))
				return (0);
			break;
		case BAN_OPER_NEQ:
			if (arg1 != NULL && !strcmp(arg1, bt.arg2))
				return (0);
			break;
		case BAN_OPER_MATCH:
			if (arg1 == NULL ||
			    pcre_exec(bt.arg2_spec, NULL, arg1, strlen(arg1),
			    0, 0, NULL, 0) < 0)
				return (0);
			break;
		case BAN_OPER_NMATCH:
			if (arg1 != NULL &&
			    pcre_exec(bt.arg2_spec, NULL, arg1, strlen(arg1),
			    0, 0, NULL, 0) >= 0)
				return (0);
			break;
		default:
			INCOMPL();
		}
	}
	return (1);
}

/*--------------------------------------------------------------------
 * Check an object against all applicable bans
 *
 * Return:
 *	-1 not all bans checked, but none of the checked matched
 *		Only if !has_req
 *	0 No bans matched, object moved to ban_start.
 *	1 Ban matched, object removed from ban list.
 */

static int
ban_check_object(struct object *o, const struct sess *sp, int has_req)
{
	struct ban *b;
	struct objcore *oc;
	struct ban * volatile b0;
	unsigned tests, skipped;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_NOTNULL(o, OBJECT_MAGIC);
	oc = o->objcore;
	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	CHECK_OBJ_NOTNULL(oc->ban, BAN_MAGIC);

	b0 = ban_start;
	CHECK_OBJ_NOTNULL(b0, BAN_MAGIC);

	if (b0 == oc->ban)
		return (0);

	/*
	 * This loop is safe without locks, because we know we hold
	 * a refcount on a ban somewhere in the list and we do not
	 * inspect the list past that ban.
	 */
	tests = 0;
	skipped = 0;
	for (b = b0; b != oc->ban; b = VTAILQ_NEXT(b, list)) {
		CHECK_OBJ_NOTNULL(b, BAN_MAGIC);
		if (b->flags & BAN_F_GONE)
			continue;
		if ((b->flags & BAN_F_LURK) &&
		    (b->flags & BAN_F_LURK) == (oc->flags & OC_F_LURK)) {
			AZ(b->flags & BAN_F_REQ);
			/* Lurker already tested this */
			continue;
		}
		if (!has_req && (b->flags & BAN_F_REQ)) {
			/*
			 * We cannot test this one, but there might
			 * be other bans that match, so we soldier on
			 */
			skipped++;
		} else if (ban_evaluate(b->spec, o->http, sp->http, &tests))
			break;
	}

	Lck_Lock(&ban_mtx);
	VSC_C_main->n_ban_obj_test++;
	VSC_C_main->n_ban_re_test += tests;

	if (b == oc->ban && skipped > 0) {
		AZ(has_req);
		Lck_Unlock(&ban_mtx);
		/*
		 * Not banned, but some tests were skipped, so we cannot know
		 * for certain that it cannot be, so we just have to give up.
		 */
		return (-1);
	}

	oc->ban->refcount--;
	VTAILQ_REMOVE(&oc->ban->objcore, oc, ban_list);
	if (b == oc->ban) {	/* not banned */
		b->flags &= ~BAN_F_LURK;
		VTAILQ_INSERT_TAIL(&b0->objcore, oc, ban_list);
		b0->refcount++;
	}
	Lck_Unlock(&ban_mtx);

	if (b == oc->ban) {	/* not banned */
		oc->ban = b0;
		oc_updatemeta(oc);
		return (0);
	} else {
		EXP_Clr(&o->exp);
		oc->ban = NULL;
		oc_updatemeta(oc);
		/* BAN also changed, but that is not important any more */
		WSP(sp, SLT_ExpBan, "%u was banned", o->xid);
		EXP_Rearm(o);
		return (1);
	}
}

int
BAN_CheckObject(struct object *o, const struct sess *sp)
{

	return (ban_check_object(o, sp, 1) > 0);
}

static struct ban *
ban_CheckLast(void)
{
	struct ban *b;

	Lck_AssertHeld(&ban_mtx);
	b = VTAILQ_LAST(&ban_head, banhead_s);
	if (b != VTAILQ_FIRST(&ban_head) && b->refcount == 0) {
		if (b->flags & BAN_F_GONE)
			VSC_C_main->n_ban_gone--;
		VSC_C_main->n_ban--;
		VSC_C_main->n_ban_retire++;
		VTAILQ_REMOVE(&ban_head, b, list);
	} else {
		b = NULL;
	}
	return (b);
}

/*--------------------------------------------------------------------
 * Ban lurker thread
 */

static int
ban_lurker_work(const struct sess *sp, unsigned pass)
{
	struct ban *b, *b0, *b2;
	struct objhead *oh;
	struct objcore *oc, *oc2;
	struct object *o;
	int i;

	AN(pass & BAN_F_LURK);
	AZ(pass & ~BAN_F_LURK);

	/* First route the last ban(s) */
	do {
		Lck_Lock(&ban_mtx);
		b2 = ban_CheckLast();
		Lck_Unlock(&ban_mtx);
		if (b2 != NULL)
			BAN_Free(b2);
	} while (b2 != NULL);

	/*
	 * Find out if we have any bans we can do something about
	 * If we find any, tag them with our pass number.
	 */
	i = 0;
	b0 = NULL;
	VTAILQ_FOREACH(b, &ban_head, list) {
		if (b->flags & BAN_F_GONE)
			continue;
		if (b->flags & BAN_F_REQ)
			continue;
		if (b == VTAILQ_LAST(&ban_head, banhead_s))
			continue;
		if (b0 == NULL)
			b0 = b;
		i++;
		b->flags &= ~BAN_F_LURK;
		b->flags |= pass;
	}
	if (params->diag_bitmap & 0x80000)
		VSL(SLT_Debug, 0, "lurker: %d actionable bans", i);
	if (i == 0)
		return (0);

	VTAILQ_FOREACH_REVERSE(b, &ban_head, banhead_s, list) {
		if (params->diag_bitmap & 0x80000)
			VSL(SLT_Debug, 0, "lurker doing %f %d",
			    ban_time(b->spec), b->refcount);
		while (1) {
			Lck_Lock(&ban_mtx);
			oc = VTAILQ_FIRST(&b->objcore);
			if (oc == NULL)
				break;
			CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
			if (params->diag_bitmap & 0x80000)
				VSL(SLT_Debug, 0, "test: %p %d %d",
				    oc, oc->flags & OC_F_LURK, pass);
			if ((oc->flags & OC_F_LURK) == pass)
				break;
			oh = oc->objhead;
			CHECK_OBJ_NOTNULL(oh, OBJHEAD_MAGIC);
			if (Lck_Trylock(&oh->mtx)) {
				Lck_Unlock(&ban_mtx);
				TIM_sleep(params->ban_lurker_sleep);
				continue;
			}
			/*
			 * See if the objcore is still on the objhead since
			 * we race against HSH_Deref() which comes in the
			 * opposite locking order.
			 */
			VTAILQ_FOREACH(oc2, &oh->objcs, list)
				if (oc == oc2)
					break;
			if (oc2 == NULL) {
				Lck_Unlock(&oh->mtx);
				Lck_Unlock(&ban_mtx);
				TIM_sleep(params->ban_lurker_sleep);
				continue;
			}
			/*
			 * If the object is busy, we can't touch
			 * it. Defer it to a later run.
			 */
			if (oc->flags & OC_F_BUSY) {
				oc->flags |= pass;
				VTAILQ_REMOVE(&b->objcore, oc, ban_list);
				VTAILQ_INSERT_TAIL(&b->objcore, oc, ban_list);
				Lck_Unlock(&oh->mtx);
				Lck_Unlock(&ban_mtx);
				continue;
			}
			/*
			 * Grab a reference to the OC and we can let go of
			 * the BAN mutex
			 */
			AN(oc->refcnt);
			oc->refcnt++;
			oc->flags &= ~OC_F_LURK;
			Lck_Unlock(&ban_mtx);
			/*
			 * Get the object and check it against all relevant bans
			 */
			o = oc_getobj(sp->wrk, oc);
			i = ban_check_object(o, sp, 0);
			if (params->diag_bitmap & 0x80000)
				VSL(SLT_Debug, 0, "lurker got: %p %d",
				    oc, i);
			if (i == -1) {
				/* Not banned, not moved */
				oc->flags |= pass;
				Lck_Lock(&ban_mtx);
				VTAILQ_REMOVE(&b->objcore, oc, ban_list);
				VTAILQ_INSERT_TAIL(&b->objcore, oc, ban_list);
				Lck_Unlock(&ban_mtx);
			}
			Lck_Unlock(&oh->mtx);
			if (params->diag_bitmap & 0x80000)
				VSL(SLT_Debug, 0, "lurker done: %p %d %d",
				    oc, oc->flags & OC_F_LURK, pass);
			(void)HSH_Deref(sp->wrk, NULL, &o);
			TIM_sleep(params->ban_lurker_sleep);
		}
		Lck_AssertHeld(&ban_mtx);
		if (!(b->flags & BAN_F_REQ)) {
			if (!(b->flags & BAN_F_GONE)) {
				b->flags |= BAN_F_GONE;
				VSC_C_main->n_ban_gone++;
			}
			if (params->diag_bitmap & 0x80000)
				VSL(SLT_Debug, 0, "lurker BAN %f now gone",
				    ban_time(b->spec));
		}
		Lck_Unlock(&ban_mtx);
		TIM_sleep(params->ban_lurker_sleep);
		if (b == b0)
			break;
	}
	return (1);
}

static void * __match_proto__(bgthread_t)
ban_lurker(struct sess *sp, void *priv)
{
	struct ban *bf;
	unsigned pass = (1 << LURK_SHIFT);

	int i = 0;
	(void)priv;
	while (1) {

		while (params->ban_lurker_sleep == 0.0) {
			/*
			 * Ban-lurker is disabled:
			 * Clean the last ban, if possible, and sleep
			 */
			Lck_Lock(&ban_mtx);
			bf = ban_CheckLast();
			Lck_Unlock(&ban_mtx);
			if (bf != NULL)
				BAN_Free(bf);
			else
				TIM_sleep(1.0);
		}

		i = ban_lurker_work(sp, pass);
		WSL_Flush(sp->wrk, 0);
		WRK_SumStat(sp->wrk);
		if (i) {
			pass += (1 << LURK_SHIFT);
			pass &= BAN_F_LURK;
			if (pass == 0)
				pass += (1 << LURK_SHIFT);
			TIM_sleep(params->ban_lurker_sleep);
		} else {
			TIM_sleep(1.0);
		}
	}
	NEEDLESS_RETURN(NULL);
}

/*--------------------------------------------------------------------
 * CLI functions to add bans
 */

static void
ccf_ban(struct cli *cli, const char * const *av, void *priv)
{
	int narg, i;
	struct ban *b;

	(void)priv;

	/* First do some cheap checks on the arguments */
	for (narg = 0; av[narg + 2] != NULL; narg++)
		continue;
	if ((narg % 4) != 3) {
		VCLI_Out(cli, "Wrong number of arguments");
		VCLI_SetResult(cli, CLIS_PARAM);
		return;
	}
	for (i = 3; i < narg; i += 4) {
		if (strcmp(av[i + 2], "&&")) {
			VCLI_Out(cli, "Found \"%s\" expected &&", av[i + 2]);
			VCLI_SetResult(cli, CLIS_PARAM);
			return;
		}
	}

	b = BAN_New();
	if (b == NULL) {
		VCLI_Out(cli, "Out of Memory");
		VCLI_SetResult(cli, CLIS_CANT);
		return;
	}
	for (i = 0; i < narg; i += 4)
		if (BAN_AddTest(cli, b, av[i + 2], av[i + 3], av[i + 4])) {
			BAN_Free(b);
			return;
		}
	BAN_Insert(b);
}

static void
ccf_ban_url(struct cli *cli, const char * const *av, void *priv)
{
	const char *aav[6];

	(void)priv;
	aav[0] = NULL;
	aav[1] = "ban";
	aav[2] = "req.url";
	aav[3] = "~";
	aav[4] = av[2];
	aav[5] = NULL;
	ccf_ban(cli, aav, priv);
}

static void
ban_render(struct cli *cli, const uint8_t *bs)
{
	struct ban_test bt;
	const uint8_t *be;

	be = bs + ban_len(bs);
	bs += 13;
	while (bs < be) {
		ban_iter(&bs, &bt);
		switch (bt.arg1) {
		case BAN_ARG_URL:
			VCLI_Out(cli, "req.url");
			break;
		case BAN_ARG_REQHTTP:
			VCLI_Out(cli, "req.http.%.*s",
			    bt.arg1_spec[0] - 1, bt.arg1_spec + 1);
			break;
		case BAN_ARG_OBJHTTP:
			VCLI_Out(cli, "obj.http.%.*s",
			    bt.arg1_spec[0] - 1, bt.arg1_spec + 1);
			break;
		default:
			INCOMPL();
		}
		switch (bt.oper) {
		case BAN_OPER_EQ:	VCLI_Out(cli, " == "); break;
		case BAN_OPER_NEQ:	VCLI_Out(cli, " != "); break;
		case BAN_OPER_MATCH:	VCLI_Out(cli, " ~ "); break;
		case BAN_OPER_NMATCH:	VCLI_Out(cli, " !~ "); break;
		default:
			INCOMPL();
		}
		VCLI_Out(cli, "%s", bt.arg2);
		if (bs < be)
			VCLI_Out(cli, " && ");
	}
}

static void
ccf_ban_list(struct cli *cli, const char * const *av, void *priv)
{
	struct ban *b, *bl;

	(void)av;
	(void)priv;

	/* Get a reference so we are safe to traverse the list */
	bl = BAN_TailRef();

	VCLI_Out(cli, "Present bans:\n");
	VTAILQ_FOREACH(b, &ban_head, list) {
		if (b == bl && !(params->diag_bitmap & 0x80000))
			break;
		VCLI_Out(cli, "%10.6f %5u%s\t", ban_time(b->spec),
		    bl == b ? b->refcount - 1 : b->refcount,
		    b->flags & BAN_F_GONE ? "G" : " ");
		ban_render(cli, b->spec);
		VCLI_Out(cli, "\n");
		if (params->diag_bitmap & 0x80000) {
			Lck_Lock(&ban_mtx);
			struct objcore *oc;
			VTAILQ_FOREACH(oc, &b->objcore, ban_list)
				VCLI_Out(cli, "     %p\n", oc);
			Lck_Unlock(&ban_mtx);
		}
	}

	BAN_TailDeref(&bl);
}

static struct cli_proto ban_cmds[] = {
	{ CLI_BAN_URL,				"", ccf_ban_url },
	{ CLI_BAN,				"", ccf_ban },
	{ CLI_BAN_LIST,				"", ccf_ban_list },
	{ NULL }
};

void
BAN_Init(void)
{

	Lck_New(&ban_mtx, lck_ban);
	CLI_AddFuncs(ban_cmds);
	assert(BAN_F_LURK == OC_F_LURK);
	AN((1 << LURK_SHIFT) & BAN_F_LURK);
	AN((2 << LURK_SHIFT) & BAN_F_LURK);

	ban_magic = BAN_New();
	AN(ban_magic);
	ban_magic->flags |= BAN_F_GONE;
	VSC_C_main->n_ban_gone++;
	BAN_Insert(ban_magic);
}
