
/*
 * $Id: htcp.c,v 1.54 2006/11/04 14:14:07 hno Exp $
 *
 * DEBUG: section 31    Hypertext Caching Protocol
 * AUTHOR: Duane Wesssels
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; 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"

typedef struct _Countstr Countstr;
typedef struct _htcpHeader htcpHeader;
typedef struct _htcpDataHeader htcpDataHeader;
typedef struct _htcpDataHeaderSquid htcpDataHeaderSquid;
typedef struct _htcpAuthHeader htcpAuthHeader;
typedef struct _htcpStuff htcpStuff;
typedef struct _htcpSpecifier htcpSpecifier;
typedef struct _htcpDetail htcpDetail;

struct _Countstr {
    u_short length;
    char *text;
};

struct _htcpHeader {
    u_short length;
    u_char major;
    u_char minor;
};

struct _htcpDataHeaderSquid {
    u_short length;
#if !WORDS_BIGENDIAN
    unsigned int opcode:4;
    unsigned int response:4;
#else
    unsigned int response:4;
    unsigned int opcode:4;
#endif
#if !WORDS_BIGENDIAN
    unsigned int reserved:6;
    unsigned int F1:1;
    unsigned int RR:1;
#else
    unsigned int RR:1;
    unsigned int F1:1;
    unsigned int reserved:6;
#endif
    u_num32 msg_id;
};

struct _htcpDataHeader {
    u_short length;
#if WORDS_BIGENDIAN
    u_char opcode:4;
    u_char response:4;
#else
    u_char response:4;
    u_char opcode:4;
#endif
#if WORDS_BIGENDIAN
    u_char reserved:6;
    u_char F1:1;
    u_char RR:1;
#else
    u_char RR:1;
    u_char F1:1;
    u_char reserved:6;
#endif
    u_num32 msg_id;
};

    /* RR == 0 --> F1 = RESPONSE DESIRED FLAG */
    /* RR == 1 --> F1 = MESSAGE OVERALL FLAG */
    /* RR == 0 --> REQUEST */
    /* RR == 1 --> RESPONSE */

struct _htcpAuthHeader {
    u_short length;
    time_t sig_time;
    time_t sig_expire;
    Countstr key_name;
    Countstr signature;
};

struct _htcpSpecifier {
    char *method;
    char *uri;
    char *version;
    char *req_hdrs;
    request_t *request;
};

struct _htcpDetail {
    char *resp_hdrs;
    char *entity_hdrs;
    char *cache_hdrs;
};

struct _htcpStuff {
    int op;
    int rr;
    int f1;
    int response;
    u_num32 msg_id;
    htcpSpecifier S;
    htcpDetail D;
};

enum {
    HTCP_NOP,
    HTCP_TST,
    HTCP_MON,
    HTCP_SET,
    HTCP_CLR,
    HTCP_END
};

static const char *const htcpOpcodeStr[] =
{
    "HTCP_NOP",
    "HTCP_TST",
    "HTCP_MON",
    "HTCP_SET",
    "HTCP_CLR",
    "HTCP_END"
};

/*
 * values for htcpDataHeader->response
 */
enum {
    AUTH_REQUIRED,
    AUTH_FAILURE,
    OPCODE_UNIMPLEMENTED,
    MAJOR_VERSION_UNSUPPORTED,
    MINOR_VERSION_UNSUPPORTED,
    INVALID_OPCODE
};

/*
 * values for htcpDataHeader->RR
 */
enum {
    RR_REQUEST,
    RR_RESPONSE
};

static u_num32 msg_id_counter = 0;
static int htcpInSocket = -1;
static int htcpOutSocket = -1;
#define N_QUERIED_KEYS 8192
static u_num32 queried_id[N_QUERIED_KEYS];
static cache_key queried_keys[N_QUERIED_KEYS][MD5_DIGEST_CHARS];
static struct sockaddr_in queried_addr[N_QUERIED_KEYS];
static MemPool *htcpSpecifierPool = NULL;
static MemPool *htcpDetailPool = NULL;

static ssize_t htcpBuildPacket(char *buf, size_t buflen, htcpStuff * stuff);
static htcpSpecifier *htcpUnpackSpecifier(char *buf, int sz);
static htcpDetail *htcpUnpackDetail(char *buf, int sz);
static ssize_t htcpBuildAuth(char *buf, size_t buflen);
static ssize_t htcpBuildCountstr(char *buf, size_t buflen, const char *s);
static ssize_t htcpBuildData(char *buf, size_t buflen, htcpStuff * stuff);
static ssize_t htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff);
static ssize_t htcpBuildOpData(char *buf, size_t buflen, htcpStuff * stuff);
static ssize_t htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff);
static ssize_t htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff);
static void htcpFreeSpecifier(htcpSpecifier * s);
static void htcpFreeDetail(htcpDetail * s);
static void htcpHandle(char *buf, int sz, struct sockaddr_in *from);
static void htcpHandleData(char *buf, int sz, struct sockaddr_in *from);
static void htcpHandleMon(htcpDataHeader *, char *buf, int sz, struct sockaddr_in *from);
static void htcpHandleNop(htcpDataHeader *, char *buf, int sz, struct sockaddr_in *from);
static void htcpHandleSet(htcpDataHeader *, char *buf, int sz, struct sockaddr_in *from);
static void htcpHandleTst(htcpDataHeader *, char *buf, int sz, struct sockaddr_in *from);
static void htcpRecv(int fd, void *data);
static void htcpSend(const char *buf, int len, struct sockaddr_in *to);
static void htcpTstReply(htcpDataHeader *, StoreEntry *, htcpSpecifier *, struct sockaddr_in *);
static void htcpHandleTstRequest(htcpDataHeader *, char *buf, int sz, struct sockaddr_in *from);
static void htcpHandleTstResponse(htcpDataHeader *, char *, int, struct sockaddr_in *);
static StoreEntry *htcpCheckHit(const htcpSpecifier *);

static int old_squid_format = 0;

static void
htcpHexdump(const char *tag, const char *s, int sz)
{
#if USE_HEXDUMP
    int i;
    int k;
    char hex[80];
    debug(31, 3) ("htcpHexdump %s\n", tag);
    memset(hex, '\0', 80);
    for (i = 0; i < sz; i++) {
	k = i % 16;
	snprintf(&hex[k * 3], 4, " %02x", (int) *(s + i));
	if (k < 15 && i < (sz - 1))
	    continue;
	debug(31, 3) ("\t%s\n", hex);
	memset(hex, '\0', 80);
    }
#endif
}

/*
 * STUFF FOR SENDING HTCP MESSAGES
 */

static ssize_t
htcpBuildAuth(char *buf, size_t buflen)
{
    htcpAuthHeader auth;
    size_t copy_sz = 0;
    assert(2 == sizeof(u_short));
    auth.length = htons(2);
    copy_sz += 2;
    if (buflen < copy_sz)
	return -1;
    xmemcpy(buf, &auth, copy_sz);
    return copy_sz;
}

static ssize_t
htcpBuildCountstr(char *buf, size_t buflen, const char *s)
{
    u_short length;
    size_t len;
    int off = 0;
    if (buflen - off < 2)
	return -1;
    if (s)
	len = strlen(s);
    else
	len = 0;
    debug(31, 3) ("htcpBuildCountstr: LENGTH = %d\n", (int) len);
    debug(31, 3) ("htcpBuildCountstr: TEXT = {%s}\n", s ? s : "<NULL>");
    length = htons((u_short) len);
    xmemcpy(buf + off, &length, 2);
    off += 2;
    if (buflen - off < len)
	return -1;
    if (len)
	xmemcpy(buf + off, s, len);
    off += len;
    return off;
}

static ssize_t
htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff)
{
    ssize_t off = 0;
    ssize_t s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.method);
    if (s < 0)
	return s;
    off += s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.uri);
    if (s < 0)
	return s;
    off += s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.version);
    if (s < 0)
	return s;
    off += s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.req_hdrs);
    if (s < 0)
	return s;
    off += s;
    debug(31, 3) ("htcpBuildSpecifier: size %d\n", (int) off);
    return off;
}

static ssize_t
htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff)
{
    ssize_t off = 0;
    ssize_t s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.resp_hdrs);
    if (s < 0)
	return s;
    off += s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.entity_hdrs);
    if (s < 0)
	return s;
    off += s;
    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.cache_hdrs);
    if (s < 0)
	return s;
    off += s;
    return off;
}

static ssize_t
htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff)
{
    switch (stuff->rr) {
    case RR_REQUEST:
	debug(31, 3) ("htcpBuildTstOpData: RR_REQUEST\n");
	return htcpBuildSpecifier(buf, buflen, stuff);
    case RR_RESPONSE:
	debug(31, 3) ("htcpBuildTstOpData: RR_RESPONSE\n");
	debug(31, 3) ("htcpBuildTstOpData: F1 = %d\n", stuff->f1);
	if (stuff->f1)		/* cache miss */
	    return 0;
	else			/* cache hit */
	    return htcpBuildDetail(buf, buflen, stuff);
    default:
	fatal_dump("htcpBuildTstOpData: bad RR value");
    }
    return 0;
}

static ssize_t
htcpBuildOpData(char *buf, size_t buflen, htcpStuff * stuff)
{
    ssize_t off = 0;
    debug(31, 3) ("htcpBuildOpData: opcode %s\n",
	htcpOpcodeStr[stuff->op]);
    switch (stuff->op) {
    case HTCP_TST:
	off = htcpBuildTstOpData(buf + off, buflen, stuff);
	break;
    case HTCP_CLR:
	/* nothing to be done */
	break;
    default:
	assert(0);
	break;
    }
    return off;
}

static ssize_t
htcpBuildData(char *buf, size_t buflen, htcpStuff * stuff)
{
    ssize_t off = 0;
    ssize_t op_data_sz;
    size_t hdr_sz = sizeof(htcpDataHeader);
    htcpDataHeader hdr;
    if (buflen < hdr_sz)
	return -1;
    off += hdr_sz;		/* skip! */
    op_data_sz = htcpBuildOpData(buf + off, buflen - off, stuff);
    if (op_data_sz < 0)
	return op_data_sz;
    off += op_data_sz;
    debug(31, 3) ("htcpBuildData: hdr.length = %d\n", (int) off);
    hdr.length = (u_short) off;
    hdr.opcode = stuff->op;
    hdr.response = stuff->response;
    hdr.RR = stuff->rr;
    hdr.F1 = stuff->f1;
    hdr.msg_id = stuff->msg_id;
    /* convert multi-byte fields */
    hdr.length = htons(hdr.length);
    hdr.msg_id = htonl(hdr.msg_id);
    if (!old_squid_format) {
	xmemcpy(buf, &hdr, hdr_sz);
    } else {
	htcpDataHeaderSquid hdrSquid;
	memset(&hdrSquid, 0, sizeof(hdrSquid));
	hdrSquid.length = hdr.length;
	hdrSquid.opcode = hdr.opcode;
	hdrSquid.response = hdr.response;
	hdrSquid.F1 = hdr.F1;
	hdrSquid.RR = hdr.RR;
	xmemcpy(buf, &hdrSquid, hdr_sz);
    }
    debug(31, 3) ("htcpBuildData: size %d\n", (int) off);
    return off;
}

/*
 * Build an HTCP packet into buf, maximum length buflen.
 * Returns the packet length, or zero on failure.
 */
static ssize_t
htcpBuildPacket(char *buf, size_t buflen, htcpStuff * stuff)
{
    ssize_t s;
    ssize_t off = 0;
    size_t hdr_sz = sizeof(htcpHeader);
    htcpHeader hdr;
    /* skip the header -- we don't know the overall length */
    if (buflen < hdr_sz) {
	return 0;
    }
    off += hdr_sz;
    s = htcpBuildData(buf + off, buflen - off, stuff);
    if (s < 0) {
	return 0;
    }
    off += s;
    s = htcpBuildAuth(buf + off, buflen - off);
    if (s < 0) {
	return 0;
    }
    off += s;
    hdr.length = htons((u_short) off);
    hdr.major = 0;
    if (old_squid_format)
	hdr.minor = 0;
    else
	hdr.minor = 1;
    xmemcpy(buf, &hdr, hdr_sz);
    debug(31, 3) ("htcpBuildPacket: size %d\n", (int) off);
    return off;
}

static void
htcpSend(const char *buf, int len, struct sockaddr_in *to)
{
    int x;
    debug(31, 3) ("htcpSend: %s/%d\n",
	inet_ntoa(to->sin_addr), (int) ntohs(to->sin_port));
    htcpHexdump("htcpSend", buf, len);
    x = comm_udp_sendto(htcpOutSocket,
	to,
	sizeof(struct sockaddr_in),
	buf,
	len);
    if (x < 0)
	debug(31, 1) ("htcpSend: FD %d sendto: %s\n", htcpOutSocket, xstrerror());
    else
	statCounter.htcp.pkts_sent++;
}

/*
 * STUFF FOR RECEIVING HTCP MESSAGES
 */

static void
htcpFreeSpecifier(htcpSpecifier * s)
{
    if (s->request)
	requestDestroy(s->request);
    memPoolFree(htcpSpecifierPool, s);
}

static void
htcpFreeDetail(htcpDetail * d)
{
    memPoolFree(htcpDetailPool, d);
}

/*
 * Unpack an HTCP SPECIFIER in place
 * This will overwrite any following AUTH block
 */
static htcpSpecifier *
htcpUnpackSpecifier(char *buf, int sz)
{
    htcpSpecifier *s = memPoolAlloc(htcpSpecifierPool);
    method_t method;

    /* Find length of METHOD */
    u_short l = ntohs(*(u_short *) buf);
    sz -= 2;
    buf += 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackSpecifier: failed to unpack METHOD\n");
	htcpFreeSpecifier(s);
	return NULL;
    }
    /* Set METHOD */
    s->method = buf;
    buf += l;
    sz -= l;

    /* Find length of URI */
    l = ntohs(*(u_short *) buf);
    sz -= 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackSpecifier: failed to unpack URI\n");
	htcpFreeSpecifier(s);
	return NULL;
    }
    /* Add terminating null to METHOD */
    *buf = '\0';
    /* Set URI */
    buf += 2;
    s->uri = buf;
    buf += l;
    sz -= l;

    /* Find length of VERSION */
    l = ntohs(*(u_short *) buf);
    sz -= 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackSpecifier: failed to unpack VERSION\n");
	htcpFreeSpecifier(s);
	return NULL;
    }
    /* Add terminating null to URI */
    *buf = '\0';
    /* Set VERSION */
    buf += 2;
    s->version = buf;
    buf += l;
    sz -= l;

    /* Find length of REQ-HDRS */
    l = ntohs(*(u_short *) buf);
    sz -= 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackSpecifier: failed to unpack REQ-HDRS\n");
	htcpFreeSpecifier(s);
	return NULL;
    }
    /* Add terminating null to URI */
    *buf = '\0';
    /* Set REQ-HDRS */
    buf += 2;
    s->req_hdrs = buf;
    buf += l;
    sz -= l;

    debug(31, 3) ("htcpUnpackSpecifier: %d bytes left\n", sz);
    /* 
     * Add terminating null to REQ-HDRS. This is possible because we allocated 
     * an extra byte when we received the packet. This will overwrite any following
     * AUTH block.
     */
    *buf = '\0';
    /*
     * Parse the request
     */
    method = urlParseMethod(s->method);
    s->request = urlParse(method == METHOD_NONE ? METHOD_GET : method, s->uri);
    return s;
}

/*
 * Unpack an HTCP DETAIL in place
 * This will overwrite any following AUTH block
 */
static htcpDetail *
htcpUnpackDetail(char *buf, int sz)
{
    htcpDetail *d = memPoolAlloc(htcpDetailPool);

    /* Find length of RESP-HDRS */
    u_short l = ntohs(*(u_short *) buf);
    sz -= 2;
    buf += 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackDetail: failed to unpack RESP-HDRS\n");
	htcpFreeDetail(d);
	return NULL;
    }
    /* Set RESP-HDRS */
    d->resp_hdrs = buf;
    buf += l;
    sz -= l;

    /* Find length of ENTITY-HDRS */
    l = ntohs(*(u_short *) buf);
    sz -= 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackDetail: failed to unpack ENTITY-HDRS\n");
	htcpFreeDetail(d);
	return NULL;
    }
    /* Add terminating null to RESP-HDRS */
    *buf = '\0';
    /* Set ENTITY-HDRS */
    buf += 2;
    d->entity_hdrs = buf;
    buf += l;
    sz -= l;

    /* Find length of CACHE-HDRS */
    l = ntohs(*(u_short *) buf);
    sz -= 2;
    if (l > sz) {
	debug(31, 1) ("htcpUnpackDetail: failed to unpack CACHE-HDRS\n");
	htcpFreeDetail(d);
	return NULL;
    }
    /* Add terminating null to ENTITY-HDRS */
    *buf = '\0';
    /* Set CACHE-HDRS */
    buf += 2;
    d->cache_hdrs = buf;
    buf += l;
    sz -= l;

    debug(31, 3) ("htcpUnpackDetail: %d bytes left\n", sz);
    /* 
     * Add terminating null to CACHE-HDRS. This is possible because we allocated 
     * an extra byte when we received the packet. This will overwrite any following
     * AUTH block.
     */
    *buf = '\0';
    return d;
}

static int
htcpAccessCheck(acl_access * acl, htcpSpecifier * s, struct sockaddr_in *from)
{
    aclCheck_t checklist;
    memset(&checklist, '\0', sizeof(checklist));
    checklist.src_addr = from->sin_addr;
    checklist.my_addr = no_addr;
    checklist.request = s->request;
    return aclCheckFast(acl, &checklist);
}

static void
htcpTstReply(htcpDataHeader * dhdr, StoreEntry * e, htcpSpecifier * spec, struct sockaddr_in *from)
{
    htcpStuff stuff;
    static char pkt[8192];
    HttpHeader hdr;
    MemBuf mb;
    Packer p;
    ssize_t pktlen;
    char *host;
    int rtt = 0;
    int hops = 0;
    int samp = 0;
    char cto_buf[128];
    memset(&stuff, '\0', sizeof(stuff));
    stuff.op = HTCP_TST;
    stuff.rr = RR_RESPONSE;
    stuff.f1 = 0;
    stuff.response = e ? 0 : 1;
    debug(31, 3) ("htcpTstReply: response = %d\n", stuff.response);
    stuff.msg_id = dhdr->msg_id;
    if (spec) {
	memBufDefInit(&mb);
	packerToMemInit(&p, &mb);
	httpHeaderInit(&hdr, hoHtcpReply);
	stuff.S.method = spec->method;
	stuff.S.uri = spec->uri;
	stuff.S.version = spec->version;
	stuff.S.req_hdrs = spec->req_hdrs;
	httpHeaderPutInt(&hdr, HDR_AGE,
	    e->timestamp <= squid_curtime ?
	    squid_curtime - e->timestamp : 0);
	httpHeaderPackInto(&hdr, &p);
	stuff.D.resp_hdrs = xstrdup(mb.buf);
	debug(31, 3) ("htcpTstReply: resp_hdrs = {%s}\n", stuff.D.resp_hdrs);
	memBufReset(&mb);
	httpHeaderReset(&hdr);
	if (e->expires > -1)
	    httpHeaderPutTime(&hdr, HDR_EXPIRES, e->expires);
	if (e->lastmod > -1)
	    httpHeaderPutTime(&hdr, HDR_LAST_MODIFIED, e->lastmod);
	httpHeaderPackInto(&hdr, &p);
	stuff.D.entity_hdrs = xstrdup(mb.buf);
	debug(31, 3) ("htcpTstReply: entity_hdrs = {%s}\n", stuff.D.entity_hdrs);
	memBufReset(&mb);
	httpHeaderReset(&hdr);
	if ((host = urlHostname(spec->uri))) {
	    netdbHostData(host, &samp, &rtt, &hops);
	    if (rtt || hops) {
		snprintf(cto_buf, 128, "%s %d %f %d",
		    host, samp, 0.001 * rtt, hops);
		httpHeaderPutExt(&hdr, "Cache-to-Origin", cto_buf);
	    }
	}
	httpHeaderPackInto(&hdr, &p);
	stuff.D.cache_hdrs = xstrdup(mb.buf);
	debug(31, 3) ("htcpTstReply: cache_hdrs = {%s}\n", stuff.D.cache_hdrs);
	memBufClean(&mb);
	httpHeaderClean(&hdr);
	packerClean(&p);
    }
    pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
    safe_free(stuff.D.resp_hdrs);
    safe_free(stuff.D.entity_hdrs);
    safe_free(stuff.D.cache_hdrs);
    if (!pktlen) {
	debug(31, 1) ("htcpTstReply: htcpBuildPacket() failed\n");
	return;
    }
    htcpSend(pkt, (int) pktlen, from);
}

static void
htcpClrReply(htcpDataHeader * dhdr, int purgeSucceeded, struct sockaddr_in *from)
{
    htcpStuff stuff;
    static char pkt[8192];
    ssize_t pktlen;

    /* If dhdr->F1 == 0, no response desired */
    if (dhdr->F1 == 0)
	return;

    memset(&stuff, '\0', sizeof(stuff));
    stuff.op = HTCP_CLR;
    stuff.rr = RR_RESPONSE;
    stuff.f1 = 0;
    stuff.response = purgeSucceeded ? 0 : 2;
    debug(31, 3) ("htcpClrReply: response = %d\n", stuff.response);
    stuff.msg_id = dhdr->msg_id;
    pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
    if (pktlen == 0) {
	debug(31, 1) ("htcpClrReply: htcpBuildPacket() failed\n");
	return;
    }
    htcpSend(pkt, (int) pktlen, from);
}

static void
htcpHandleNop(htcpDataHeader * hdr, char *buf, int sz, struct sockaddr_in *from)
{
    debug(31, 3) ("htcpHandleNop: Unimplemented\n");
}

static StoreEntry *
htcpCheckHit(const htcpSpecifier * s)
{
    request_t *request = s->request;
    StoreEntry *e = NULL, *hit = NULL;
    char *blk_end;
    if (NULL == request) {
	debug(31, 3) ("htcpCheckHit: NO; failed to parse URL\n");
	return NULL;
    }
    blk_end = s->req_hdrs + strlen(s->req_hdrs);
    if (!httpHeaderParse(&request->header, s->req_hdrs, blk_end)) {
	debug(31, 3) ("htcpCheckHit: NO; failed to parse request headers\n");
	goto miss;
    }
    e = storeGetPublicByRequest(request);
    if (NULL == e) {
	debug(31, 3) ("htcpCheckHit: NO; public object not found\n");
	goto miss;
    }
    if (!storeEntryValidToSend(e)) {
	debug(31, 3) ("htcpCheckHit: NO; entry not valid to send\n");
	goto miss;
    }
    if (refreshCheckHTCP(e, request)) {
	debug(31, 3) ("htcpCheckHit: NO; cached response is stale\n");
	goto miss;
    }
    debug(31, 3) ("htcpCheckHit: YES!?\n");
    hit = e;
  miss:
    return hit;
}

static void
htcpClrStoreEntry(StoreEntry * e)
{
    debug(31, 4) ("htcpClrStoreEntry: Clearing store for entry: %s\n", storeUrl(e));
    storeRelease(e);
}

static int
htcpClrStore(const htcpSpecifier * s)
{
    request_t *request = s->request;
    char *blk_end;
    StoreEntry *e = NULL;
    int released = 0;

    if (request == NULL) {
	debug(31, 3) ("htcpClrStore: failed to parse URL\n");
	return -1;
    }
    /* Parse request headers */
    blk_end = s->req_hdrs + strlen(s->req_hdrs);
    if (!httpHeaderParse(&request->header, s->req_hdrs, blk_end)) {
	debug(31, 2) ("htcpClrStore: failed to parse request headers\n");
	return -1;
    }
    /* Lookup matching entries. This matches both GET and HEAD */
    while ((e = storeGetPublicByRequest(request)) != NULL) {
	if (e != NULL) {
	    htcpClrStoreEntry(e);
	    released++;
	}
    }

    if (released) {
	debug(31, 4) ("htcpClrStore: Cleared %d matching entries\n", released);
	return 1;
    } else {
	debug(31, 4) ("htcpClrStore: No matching entry found\n");
	return 0;
    }
}

static void
htcpHandleTst(htcpDataHeader * hdr, char *buf, int sz, struct sockaddr_in *from)
{
    debug(31, 3) ("htcpHandleTst: sz = %d\n", (int) sz);
    if (hdr->RR == RR_REQUEST)
	htcpHandleTstRequest(hdr, buf, sz, from);
    else
	htcpHandleTstResponse(hdr, buf, sz, from);
}

static void
htcpHandleTstResponse(htcpDataHeader * hdr, char *buf, int sz, struct sockaddr_in *from)
{
    htcpReplyData htcpReply;
    cache_key *key = NULL;
    struct sockaddr_in *peer;
    htcpDetail *d = NULL;
    char *t;

    if (queried_id[hdr->msg_id % N_QUERIED_KEYS] != hdr->msg_id) {
	debug(31, 2) ("htcpHandleTstResponse: No matching query id '%d' (expected %d) from '%s'\n", hdr->msg_id, queried_id[hdr->msg_id % N_QUERIED_KEYS], inet_ntoa(from->sin_addr));
	return;
    }
    key = queried_keys[hdr->msg_id % N_QUERIED_KEYS];
    if (!key) {
	debug(31, 1) ("htcpHandleTstResponse: No query key for response id '%d' from '%s'\n", hdr->msg_id, inet_ntoa(from->sin_addr));
	return;
    }
    peer = &queried_addr[hdr->msg_id % N_QUERIED_KEYS];
    if (peer->sin_addr.s_addr != from->sin_addr.s_addr || peer->sin_port != from->sin_port) {
	debug(31, 1) ("htcpHandleTstResponse: Unexpected response source %s\n", inet_ntoa(from->sin_addr));
	return;
    }
    if (hdr->F1 == 1) {
	debug(31, 2) ("htcpHandleTstResponse: error condition, F1/MO == 1\n");
	return;
    }
    memset(&htcpReply, '\0', sizeof(htcpReply));
    httpHeaderInit(&htcpReply.hdr, hoHtcpReply);
    htcpReply.msg_id = hdr->msg_id;
    debug(31, 3) ("htcpHandleTstResponse: msg_id = %d\n", (int) htcpReply.msg_id);
    htcpReply.hit = hdr->response ? 0 : 1;
    if (hdr->F1) {
	debug(31, 3) ("htcpHandleTstResponse: MISS\n");
    } else {
	debug(31, 3) ("htcpHandleTstResponse: HIT\n");
	d = htcpUnpackDetail(buf, sz);
	if (d == NULL) {
	    debug(31, 1) ("htcpHandleTstResponse: bad DETAIL\n");
	    return;
	}
	if ((t = d->resp_hdrs))
	    httpHeaderParse(&htcpReply.hdr, t, t + strlen(t));
	if ((t = d->entity_hdrs))
	    httpHeaderParse(&htcpReply.hdr, t, t + strlen(t));
	if ((t = d->cache_hdrs))
	    httpHeaderParse(&htcpReply.hdr, t, t + strlen(t));
    }
    debug(31, 3) ("htcpHandleTstResponse: key (%p) %s\n", key, storeKeyText(key));
    neighborsHtcpReply(key, &htcpReply, from);
    httpHeaderClean(&htcpReply.hdr);
    if (d)
	htcpFreeDetail(d);
}

static void
htcpHandleTstRequest(htcpDataHeader * dhdr, char *buf, int sz, struct sockaddr_in *from)
{
    /* buf should be a SPECIFIER */
    htcpSpecifier *s;
    StoreEntry *e;
    if (sz == 0) {
	debug(31, 3) ("htcpHandleTst: nothing to do\n");
	return;
    }
    if (dhdr->F1 == 0)
	return;
    s = htcpUnpackSpecifier(buf, sz);
    if (NULL == s) {
	debug(31, 2) ("htcpHandleTstRequest: htcpUnpackSpecifier failed\n");
	return;
    }
    if (!s->request) {
	debug(31, 2) ("htcpHandleTstRequest: failed to parse request\n");
	htcpFreeSpecifier(s);
	return;
    }
    if (!htcpAccessCheck(Config.accessList.htcp, s, from)) {
	debug(31, 2) ("htcpHandleTstRequest: Access denied\n");
	htcpFreeSpecifier(s);
	return;
    }
    debug(31, 3) ("htcpHandleTstRequest: %s %s %s\n",
	s->method,
	s->uri,
	s->version);
    debug(31, 3) ("htcpHandleTstRequest: %s\n", s->req_hdrs);
    if ((e = htcpCheckHit(s)))
	htcpTstReply(dhdr, e, s, from);		/* hit */
    else
	htcpTstReply(dhdr, NULL, NULL, from);	/* cache miss */
    htcpFreeSpecifier(s);
}

static void
htcpHandleMon(htcpDataHeader * hdr, char *buf, int sz, struct sockaddr_in *from)
{
    debug(31, 3) ("htcpHandleMon: Unimplemented\n");
}

static void
htcpHandleSet(htcpDataHeader * hdr, char *buf, int sz, struct sockaddr_in *from)
{
    debug(31, 3) ("htcpHandleSet: Unimplemented\n");
}

static void
htcpHandleClr(htcpDataHeader * hdr, char *buf, int sz, struct sockaddr_in *from)
{
    htcpSpecifier *s;
    /* buf[0/1] is reserved and reason */
    int reason = buf[1] << 4;
    debug(31, 3) ("htcpHandleClr: reason=%d\n", reason);
    buf += 2;
    sz -= 2;

    /* buf should be a SPECIFIER */
    if (sz == 0) {
	debug(31, 4) ("htcpHandleClr: nothing to do\n");
	return;
    }
    s = htcpUnpackSpecifier(buf, sz);
    if (NULL == s) {
	debug(31, 3) ("htcpHandleClr: htcpUnpackSpecifier failed\n");
	return;
    }
    if (!htcpAccessCheck(Config.accessList.htcp_clr, s, from)) {
	debug(31, 2) ("htcpHandleClr: Access denied\n");
	htcpFreeSpecifier(s);
	return;
    }
    debug(31, 5) ("htcpHandleClr: %s %s %s\n",
	s->method,
	s->uri,
	s->version);
    debug(31, 5) ("htcpHandleClr: request headers: %s\n", s->req_hdrs);

    /* Release objects from cache
     * analog to clientPurgeRequest in client_side.c
     */
    switch (htcpClrStore(s)) {
    case 1:
	htcpClrReply(hdr, 1, from);	/* hit */
	break;
    case 0:
	htcpClrReply(hdr, 0, from);	/* miss */
	break;
    default:
	break;
    }

    htcpFreeSpecifier(s);
}

static void
htcpHandleData(char *buf, int sz, struct sockaddr_in *from)
{
    htcpDataHeader hdr;
    if (sz < sizeof(htcpDataHeader)) {
	debug(31, 1) ("htcpHandleData: msg size less than htcpDataHeader size\n");
	return;
    }
    if (!old_squid_format) {
	xmemcpy(&hdr, buf, sizeof(hdr));
    } else {
	htcpDataHeaderSquid hdrSquid;
	xmemcpy(&hdrSquid, buf, sizeof(hdrSquid));
	hdr.length = hdrSquid.length;
	hdr.opcode = hdrSquid.opcode;
	hdr.response = hdrSquid.response;
	hdr.F1 = hdrSquid.F1;
	hdr.RR = hdrSquid.RR;
	hdr.reserved = 0;
	hdr.msg_id = hdrSquid.msg_id;
    }
    hdr.length = ntohs(hdr.length);
    hdr.msg_id = ntohl(hdr.msg_id);
    debug(31, 3) ("htcpHandleData: sz = %d\n", sz);
    debug(31, 3) ("htcpHandleData: length = %d\n", (int) hdr.length);
    if (hdr.opcode >= HTCP_END) {
	debug(31, 1) ("htcpHandleData: client %s, opcode %d out of range\n",
	    inet_ntoa(from->sin_addr),
	    (int) hdr.opcode);
	return;
    }
    debug(31, 3) ("htcpHandleData: opcode = %d %s\n",
	(int) hdr.opcode, htcpOpcodeStr[hdr.opcode]);
    debug(31, 3) ("htcpHandleData: response = %d\n", (int) hdr.response);
    debug(31, 3) ("htcpHandleData: F1 = %d\n", (int) hdr.F1);
    debug(31, 3) ("htcpHandleData: RR = %d\n", (int) hdr.RR);
    debug(31, 3) ("htcpHandleData: msg_id = %d\n", (int) hdr.msg_id);
    if (sz < hdr.length) {
	debug(31, 1) ("htcpHandleData: sz < hdr.length\n");
	return;
    }
    /*
     * set sz = hdr.length so we ignore any AUTH fields following
     * the DATA.
     */
    sz = (int) hdr.length;
    buf += sizeof(htcpDataHeader);
    sz -= sizeof(htcpDataHeader);
    debug(31, 3) ("htcpHandleData: sz = %d\n", sz);
    htcpHexdump("htcpHandleData", buf, sz);
    switch (hdr.opcode) {
    case HTCP_NOP:
	htcpHandleNop(&hdr, buf, sz, from);
	break;
    case HTCP_TST:
	htcpHandleTst(&hdr, buf, sz, from);
	break;
    case HTCP_MON:
	htcpHandleMon(&hdr, buf, sz, from);
	break;
    case HTCP_SET:
	htcpHandleSet(&hdr, buf, sz, from);
	break;
    case HTCP_CLR:
	htcpHandleClr(&hdr, buf, sz, from);
	break;
    default:
	return;
    }
}

static void
htcpHandle(char *buf, int sz, struct sockaddr_in *from)
{
    htcpHeader htcpHdr;
    if (sz < sizeof(htcpHeader)) {
	debug(31, 1) ("htcpHandle: msg size less than htcpHeader size\n");
	return;
    }
    htcpHexdump("htcpHandle", buf, sz);
    xmemcpy(&htcpHdr, buf, sizeof(htcpHeader));
    htcpHdr.length = ntohs(htcpHdr.length);
    if (htcpHdr.minor == 0)
	old_squid_format = 1;
    else
	old_squid_format = 0;
    debug(31, 3) ("htcpHandle: htcpHdr.length = %d\n", (int) htcpHdr.length);
    debug(31, 3) ("htcpHandle: htcpHdr.major = %d\n", (int) htcpHdr.major);
    debug(31, 3) ("htcpHandle: htcpHdr.minor = %d\n", (int) htcpHdr.minor);
    if (sz != htcpHdr.length) {
	debug(31, 1) ("htcpHandle: sz/%d != htcpHdr.length/%d from %s:%d\n",
	    sz, htcpHdr.length,
	    inet_ntoa(from->sin_addr), (int) ntohs(from->sin_port));
	return;
    }
    if (htcpHdr.major != 0) {
	debug(31, 1) ("htcpHandle: Unknown major version %d from %s:%d\n",
	    htcpHdr.major,
	    inet_ntoa(from->sin_addr), (int) ntohs(from->sin_port));
	return;
    }
    buf += sizeof(htcpHeader);
    sz -= sizeof(htcpHeader);
    htcpHandleData(buf, sz, from);
}

static void
htcpRecv(int fd, void *data)
{
    static char buf[8192];
    int len;
    static struct sockaddr_in from;
    socklen_t flen = sizeof(struct sockaddr_in);
    memset(&from, '\0', flen);
    statCounter.syscalls.sock.recvfroms++;
    /* Receive up to 8191 bytes, leaving room for a null */
    len = recvfrom(fd, buf, sizeof(buf) - 1, 0, (struct sockaddr *) &from, &flen);
    debug(31, 3) ("htcpRecv: FD %d, %d bytes from %s:%d\n",
	fd, len, inet_ntoa(from.sin_addr), ntohs(from.sin_port));
    if (len)
	statCounter.htcp.pkts_recv++;
    htcpHandle(buf, len, &from);
    commSetSelect(fd, COMM_SELECT_READ, htcpRecv, NULL, 0);
}

/*
 * ======================================================================
 * PUBLIC FUNCTIONS
 * ======================================================================
 */

void
htcpInit(void)
{
    if (Config.Port.htcp <= 0) {
	debug(31, 1) ("HTCP Disabled.\n");
	return;
    }
    enter_suid();
    htcpInSocket = comm_open(SOCK_DGRAM,
	IPPROTO_UDP,
	Config.Addrs.udp_incoming,
	Config.Port.htcp,
	COMM_NONBLOCKING,
	"HTCP Socket");
    leave_suid();
    if (htcpInSocket < 0)
	fatal("Cannot open HTCP Socket");
    commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0);
    debug(31, 1) ("Accepting HTCP messages on port %d, FD %d.\n",
	(int) Config.Port.htcp, htcpInSocket);
    if (Config.Addrs.udp_outgoing.s_addr != no_addr.s_addr) {
	enter_suid();
	htcpOutSocket = comm_open(SOCK_DGRAM,
	    IPPROTO_UDP,
	    Config.Addrs.udp_outgoing,
	    Config.Port.htcp,
	    COMM_NONBLOCKING,
	    "Outgoing HTCP Socket");
	leave_suid();
	if (htcpOutSocket < 0)
	    fatal("Cannot open Outgoing HTCP Socket");
	commSetSelect(htcpOutSocket, COMM_SELECT_READ, htcpRecv, NULL, 0);
	debug(31, 1) ("Outgoing HTCP messages on port %d, FD %d.\n",
	    (int) Config.Port.htcp, htcpOutSocket);
	fd_note(htcpInSocket, "Incoming HTCP socket");
    } else {
	htcpOutSocket = htcpInSocket;
    }
    if (!htcpSpecifierPool) {
	htcpSpecifierPool = memPoolCreate("htcpSpecifier", sizeof(htcpSpecifier));
	htcpDetailPool = memPoolCreate("htcpDetail", sizeof(htcpDetail));
    }
}

void
htcpQuery(StoreEntry * e, request_t * req, peer * p)
{
    cache_key *save_key;
    static char pkt[8192];
    ssize_t pktlen;
    char vbuf[32];
    htcpStuff stuff;
    HttpHeader hdr;
    Packer pa;
    MemBuf mb;
    http_state_flags flags;

    if (htcpInSocket < 0)
	return;

    old_squid_format = p->options.htcp_oldsquid;
    memset(&flags, '\0', sizeof(flags));
    snprintf(vbuf, sizeof(vbuf), "%d/%d",
	req->http_ver.major, req->http_ver.minor);
    stuff.op = HTCP_TST;
    stuff.rr = RR_REQUEST;
    stuff.f1 = 1;
    stuff.response = 0;
    stuff.msg_id = ++msg_id_counter;
    stuff.S.method = (char *) RequestMethodStr[req->method];
    stuff.S.uri = (char *) storeUrl(e);
    stuff.S.version = vbuf;
    httpBuildRequestHeader(req, req, e, &hdr, flags);
    memBufDefInit(&mb);
    packerToMemInit(&pa, &mb);
    httpHeaderPackInto(&hdr, &pa);
    httpHeaderClean(&hdr);
    packerClean(&pa);
    stuff.S.req_hdrs = mb.buf;
    pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
    memBufClean(&mb);
    if (!pktlen) {
	debug(31, 1) ("htcpQuery: htcpBuildPacket() failed\n");
	return;
    }
    htcpSend(pkt, (int) pktlen, &p->in_addr);
    queried_id[stuff.msg_id % N_QUERIED_KEYS] = stuff.msg_id;
    save_key = queried_keys[stuff.msg_id % N_QUERIED_KEYS];
    storeKeyCopy(save_key, e->hash.key);
    queried_addr[stuff.msg_id % N_QUERIED_KEYS] = p->in_addr;
    debug(31, 3) ("htcpQuery: key (%p) %s\n", save_key, storeKeyText(save_key));
}

/*  
 * htcpSocketShutdown only closes the 'in' socket if it is
 * different than the 'out' socket.
 */
void
htcpSocketShutdown(void)
{
    if (htcpInSocket < 0)
	return;
    if (htcpInSocket != htcpOutSocket) {
	debug(12, 1) ("FD %d Closing HTCP socket\n", htcpInSocket);
	comm_close(htcpInSocket);
    }
    /*      
     * Here we set 'htcpInSocket' to -1 even though the HTCP 'in'
     * and 'out' sockets might be just one FD.  This prevents this
     * function from executing repeatedly.  When we are really ready to
     * exit or restart, main will comm_close the 'out' descriptor.
     */
    htcpInSocket = -1;
    /*      
     * Normally we only write to the outgoing HTCP socket, but
     * we also have a read handler there to catch messages sent
     * to that specific interface.  During shutdown, we must
     * disable reading on the outgoing socket.
     */
    /* XXX Don't we need this handler to read replies while shutting down?
     * I think there should be a separate hander for reading replies..
     */
    assert(htcpOutSocket > -1);
    commSetSelect(htcpOutSocket, COMM_SELECT_READ, NULL, NULL, 0);
}

void
htcpSocketClose(void)
{
    htcpSocketShutdown();
    if (htcpOutSocket > -1) {
	debug(12, 1) ("FD %d Closing HTCP socket\n", htcpOutSocket);
	comm_close(htcpOutSocket);
	htcpOutSocket = -1;
    }
}
