/*
    Sieve: A proxy server filter for the Scoop web log system.
    Copyright (C) 2005 Richard Heurtley

    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-1307  USA

    History:

    2005-02-09: v0.01: Initial release 
    2005-02-09: v0.02: Fixed some bugs
    2005-02-10: v0.03: Added configuration file
    2005-02-15: v0.04: Added (sieve) link
    2005-03-23: v0.05: Increased main buffer size to 2MB
*/
/**---------------------------------------------------------------------------*/
#ifdef __WATCOMC__
#define WINDOWS
#endif

#ifdef _MSC_VER
#define WINDOWS
#endif

#ifndef WINDOWS
#define LINUX
#endif
/**---------------------------------------------------------------------------*/
#ifdef WINDOWS
#include <winsock2.h>
#endif

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef LINUX
#include <arpa/inet.h>
#include <asm/errno.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#endif
/**---------------------------------------------------------------------------*/
//#define DEBUG

#define memeq !memcmp
#define streq !strcmp

#ifdef LINUX
#define SOCKET int
#define WSAGetLastError() errno
#define closesocket close
#endif
/**---------------------------------------------------------------------------*/
typedef struct
{
 double score;
 int len;
 char name[64+1];
} FILTER;

#define NFILTERS (64)

typedef struct
{
 int bufferlen;
 int headerlen;
 int bodylen;
 int nlines;
 char *bodyp;
 char *lines[1024];
 char header[65536];
 char buffer[2 * 1024 * 1024];
} LOCAL;

static LOCAL l;
/**---------------------------------------------------------------------------*/
int BigSend(SOCKET socket, char *bufferp, int bufferlen, int *nsentp)
{
 int rv = 0;
 int n;
 int nbytes;
 int nsent;
 int remain;
 char *p;

 p = bufferp;
 remain = bufferlen;
 nsent = 0;

 while (remain)
 {
  if (remain > 32768)
   nbytes = 32786;
  else
   nbytes = remain;

  n = send(socket, p, nbytes, 0);
  if (n != nbytes)
  {
   printf("Error while transmitting.\n");
   printf("Tried to send %d bytes, sent %d bytes.\n", nbytes, n);
   rv = 1;
   goto EGRESS;
  }

  nsent += n;
  p += n;
  remain -= n;
 }

EGRESS:

 if (nsentp)
  *nsentp = nsent;

 return(rv);
}
/**---------------------------------------------------------------------------*/
char *TimeStamp(void)
{
 time_t timet;
 static char text[4+1+2+1+2 +1+ 2+1+2+1+2 +1];

 timet = time(0);
 strftime(text, sizeof(text), "%Y-%m-%d %H:%M:%S", localtime(&timet));

 return(text);
}
/**---------------------------------------------------------------------------*/
// parse a buffer into an array of pointers to lines

void Parse(char *bufferp, int bufferlen, char *lines[], int *nlinesp)
{
 int nlines;
 char *p;
 char *endp;

 endp = &bufferp[bufferlen];		// get end of buffer

 *endp = 0;				// terminate buffer

 for (p = bufferp; p < endp; p++)	// null out CRs
 {
  if (*p == '\r')
   *p = 0;
 }

 nlines = 0;
 for (p = bufferp; p < endp; p++)
 {
  lines[nlines++] = p;			// note line
  for (; p < endp && *p != '\n'; p++);	// find terminator
  *p = 0;				// terminate line
 }

 *nlinesp = nlines;

 return;
}
/**---------------------------------------------------------------------------*/
int Receive(SOCKET socket, char *remotep, char *filenamep)
{
 int rv = 0;
 int bufferfree;
 int packetlen;
 int line;
 int remain;
 char *bufferp;
 char *linep;
#ifdef DEBUG
 FILE *filep = 0;
#endif

#ifndef DEBUG
 filenamep = 0;
#endif
 
#ifdef DEBUG
 filep = fopen(filenamep, "ab");
 fwrite("\r\n--------\r\n", 1, 2+8+2, filep);
#endif

 remain = -1;
 l.bufferlen = 0;
 l.headerlen = 0;
 bufferp = l.buffer;
 bufferfree = sizeof(l.buffer);

 for (;;)					// multiple packets
 {
  packetlen = recv(socket, bufferp, bufferfree, 0);
// $$$ errors
  if (!packetlen || packetlen == bufferfree)
  {
   l.bufferlen = 0;
   goto EGRESS;
  }

#ifdef DEBUG
  fwrite(bufferp, 1, packetlen, filep);
  fflush(filep);
#endif

  printf("%s %8d %s recv packet %d\n", TimeStamp(), packetlen, remotep, remain);
  fflush(stdout);

  bufferp += packetlen;
  l.bufferlen += packetlen;
  bufferfree -= packetlen;

  if (!l.headerlen)				// if no header length
  {
   for						// find line break
   (
    l.bodyp = l.buffer;
    l.bodyp <= bufferp-4 && !memeq(l.bodyp, "\r\n\r\n", 4);
    l.bodyp++
   );

   if (l.bodyp > bufferp-4)			// if no line break
    continue;					// get next packet

   l.bodyp += 4;				// get start of body
   l.headerlen = l.bodyp - l.buffer;		// get header length

   printf("%s %8d %s recv header %d\n", TimeStamp(), l.headerlen, remotep, remain);
   fflush(stdout);

   packetlen = l.bufferlen - l.headerlen;	// discount header from packet

   printf("%s %8d %s adj. packet %d\n", TimeStamp(), packetlen, remotep, remain);
   fflush(stdout);
  
   memcpy(l.header, l.buffer, l.headerlen);	// make local copy
   Parse(l.header, l.headerlen, l.lines, &l.nlines);

   for (line = 0; line < l.nlines; line++)	// find content length spec
   {
    linep = l.lines[line];
//                   "1234567890123456"
    if (memeq(linep, "Content-Length: ", 16))
     break;
   }

   if (line >= l.nlines)			// if no content length
    goto EGRESS;

   remain = atoi(&linep[16]);			// get content length

   printf("%s %8d %s recv body spec %d\n", TimeStamp(), remain, remotep, remain);
   fflush(stdout);

  }						// if (!l.headerlen)

  remain -= packetlen;				// discount remainder
  if (!remain)					// if read all data
   break;					// break out of loop

 }						// for (packet = 0; ; packet++)

EGRESS:

 l.bodylen = l.bufferlen - l.headerlen;

 if (l.bodylen)
 {
  printf("%s %8d %s recv body %d\n", TimeStamp(), l.bodylen, remotep, remain);
  fflush(stdout);
 }

#ifdef DEBUG
 if (filep)
  fclose(filep);
#endif

 return(rv);
}
/**---------------------------------------------------------------------------*/
void Append(char **dstpp, char *srcp)
{
 int nbytes;
 char *dstp;

 dstp = *dstpp;

 nbytes = strlen(srcp);
 memcpy(dstp, srcp, nbytes);
 dstp += nbytes;

 *dstpp = dstp;

 return;
}
/**---------------------------------------------------------------------------*/
void AppendF(char **dstpp, char *formatp, ...)
{
 va_list valist;
 static char text[1024];

 va_start(valist, formatp);
 vsprintf(text, formatp, valist);
 va_end(valist);

 Append(dstpp, text);

 return;
}
/**---------------------------------------------------------------------------*/
int main(void)
{
 int rv = 0;
 int rc;
 int n;
 int len;
 int line;
 int nposts;
 int nnuked;
 int header2len;
 int port;
 int nfilters;
 int filter;
 double score;
 double nonescore;
 double sievescore;
 char *p;
 char *q;
 char *parenp;
 char *posterendp;
 char *linep;
 char *commentp;
 char *commentendp;
 char *scanp;
 char *scorep;
 char *posterp;
 char *bodyendp;
 char *versionp;
 FILE *filep;
 FILTER *filterp;
 FILTER *starfilterp;

 struct hostent *hostentp;

 static struct sockaddr_in sockaddrbrowser;
 static struct sockaddr_in sockaddrlocal;
 static struct sockaddr_in sockaddrserver;

 static SOCKET socketlisten;
 static SOCKET socketbrowser;
 static SOCKET socketserver;

#ifdef WINDOWS
 static WSADATA wsadata;
#endif

 static char hostname[256];
 static char servername[256];
 static char header2[65536];
 static char filterlink[256];
 static FILTER filters[NFILTERS];
/**---------------------------------------------------------------------------*/
 printf("Sieve v0.05 2005-03-23\n");
 printf("\n");
/**---------------------------------------------------------------------------*/
// initialize networking subsystem

#ifdef WINDOWS

 rc = WSAStartup(MAKEWORD(1,1), &wsadata);		// version 1.1
 if (rc)
 {
  rc = WSAGetLastError();
  printf("Unable to start socket service: rc = %d.\n", rc);
  rv = 1;
  goto EGRESS;
 }

#endif
/**---------------------------------------------------------------------------*/
// parse configuration file

// get default values

 strcpy(servername, "www.ip-wars.net");
 gethostname(hostname, sizeof(hostname));
 port = 4004;
 nonescore = 0.0;
 nfilters = 0;
 starfilterp = 0;
 sievescore = 0.0;

// read file

 filep = fopen("sieve.conf", "r");
 if (!filep)
  goto NOCONFIG;
 l.bufferlen = fread(l.buffer, 1, sizeof(l.buffer), filep);
 fclose(filep);

 if (!l.bufferlen || l.bufferlen == sizeof(l.buffer))
  goto NOCONFIG;

 l.buffer[l.bufferlen] = 0;	// terminate buffer

// reduce whitespace

 for (p = l.buffer, q = p+1; *p; p++, q++)
 {
  if (*p == '\t')
   *p = ' ';

  if (*p != ' ')
   continue;

  if (*q == ' ' || *q == '\r' || *q == '\n' || *q == 0)
  {
   strcpy(p, q);
   p--;
   q--;
  }
 }

// parse file

 Parse(l.buffer, l.bufferlen, l.lines, &l.nlines);

 for (line = 0; line < l.nlines; line++)
 {
  linep = l.lines[line];

  if (linep[0] == '#')			// skip comments
   continue;

  if (memeq(linep, "server", 6))
   strcpy(servername, &linep[6+1]);
  else
  if (memeq(linep, "host", 4))
   strcpy(hostname, &linep[4+1]);
  else
  if (memeq(linep, "port", 4))
   port = atoi(&linep[4+1]);
  else
  if (memeq(linep, "sievescore", 10))
   sievescore = atof(&linep[10+1]);
  else
  if (memeq(linep, "none", 4))
   nonescore = atof(&linep[4+1]);
  else
  if (isdigit(linep[0]))
  {
   score = atof(linep);
   p = strchr(linep, ' ');
   if (!p)
    continue;
   p++;
   if (!*p)
    continue;

   if (nfilters >= NFILTERS)
    continue;
   filterp = &filters[nfilters];
   filterp->score = score;
   strcpy(filterp->name, p);
   filterp->len = strlen(filterp->name);
   nfilters++;

   if (streq(filterp->name, "*"))		// note catchall
    starfilterp = filterp;
  }
 }

NOCONFIG:

// if no PR filter then add one

 p = "Potential Recruit";

 for (filter = 0; filter < nfilters; filter++)
 {
  filterp = &filters[filter];
  if (streq(filterp->name, p))
   break;
 }
 if (filter >= nfilters && nfilters < NFILTERS)
 {
  filterp = &filters[nfilters];
  filterp->score = 3.0;
  strcpy(filterp->name, p);
  filterp->len = strlen(filterp->name);
  nfilters++;
 }
/**---------------------------------------------------------------------------*/
// report configuration

 printf("servername: \"%s\"\n", servername);
 printf("  hostname: \"%s\"\n", hostname);
 printf("      port: %d\n", port);
 printf(" nonescore: %4.2f\n", nonescore);
 printf("sievescore: %4.2f\n", sievescore);
//      "123456#.##:
 for (filter = 0; filter < nfilters; filter++)
 {
  filterp = &filters[filter];
//       "123456#.##
  printf("      %4.2f: \"%s\"\n", filterp->score, filterp->name);
 }
 printf("\n");
/**---------------------------------------------------------------------------*/
// get web server address

 hostentp = gethostbyname(servername);
 if (!hostentp)
 {
  rc = WSAGetLastError();
  printf("Unable to resolve server \"%s\": rc = %d.\n", servername, rc);
  rv = 1;
  goto EGRESS;
 }

 sockaddrserver.sin_family = AF_INET;
 sockaddrserver.sin_port = htons(80);
 memcpy(&sockaddrserver.sin_addr, hostentp->h_addr, hostentp->h_length);

 printf("Server \"%s\" resolves to \"%s\"\n", servername, inet_ntoa(*(struct in_addr *)hostentp->h_addr));
 printf("\n");
/**---------------------------------------------------------------------------*/
// allocate listening socket

 socketlisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#ifdef WINDOWS
 if (socketlisten == INVALID_SOCKET)
#else
 if (socketlisten < 0)
#endif
 {
  rc = WSAGetLastError();
  printf("Unable to create socket: rc = %d.\n", rc);
  rv = 1;
  goto EGRESS;
 }

// allow quick reuse of the socket

#ifdef LINUX

 n = 1;
 setsockopt(socketlisten, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));

#endif

// configure listening socket

 p = strchr(hostname, '.');
 if (!p)
 {
  printf("Warning: The hostname of this computer \"%s\" does not contain a dot ('.').\n", hostname);
  printf("Cookies cannot be set on a computer if its hostname does not contain a dot.\n");
  printf("I have taken the liberty of appending \".localdomain\" to the hostname.\n");
  printf("This probably won't resolve and you may have to add a line to your hosts file.\n");
  printf("\n");
  strcat(hostname, ".localdomain");
 }

 hostentp = gethostbyname(hostname);
 if (!hostentp)
 {
  rc = WSAGetLastError();
  printf("Unable to resolve hostname \"%s\": rc = %d.\n", hostname, rc);
  rv = 1;
  goto EGRESS;
 }

 sockaddrlocal.sin_family = AF_INET;
 sockaddrlocal.sin_port = htons((unsigned short)port);
 memcpy(&sockaddrlocal.sin_addr, hostentp->h_addr, hostentp->h_length);

 printf("Hostname \"%s\" resolves to \"%s\"\n", hostname, inet_ntoa(*(struct in_addr *)hostentp->h_addr));
 printf("\n");

// bind local address to listening socket

 rc = bind(socketlisten, (struct sockaddr *)&sockaddrlocal, sizeof(sockaddrlocal));	
 if (rc)
 {
  rc = WSAGetLastError();
  printf("Unable to bind address to socket: rc = %d.\n", rc);
  rv = 1;
  goto EGRESS;
 }

// listen for connections

 rc = listen(socketlisten, 1);
 if (rc)
 {
  rc = WSAGetLastError();
  printf("Unable to listen to socket: rc = %d.\n", rc);
  rv = 1;
  goto EGRESS;
 }

 for (;;)				// infinite loop of connections
 {
/**---------------------------------------------------------------------------*/
// accept connection from browser

  n = sizeof(sockaddrbrowser);
  socketbrowser = accept(socketlisten, (struct sockaddr *)&sockaddrbrowser, &n);
#ifdef WINDOWS
  if (socketbrowser == SOCKET_ERROR)
#else
  if (socketbrowser < 0)
#endif
  {
   rc = WSAGetLastError();
   printf("Unable to accept connection: rc = %d.\n", rc);
   rv = 1;
   goto EGRESS;
  }

// receive request from browser

  rv = Receive(socketbrowser, "browser", "request.txt");
  if (rv)
  {
   printf("Error receiving data from browser.\n");
   goto EGRESS;
  }

  if (!l.nlines)				// header parsed by Receive()
  {
   header2len = l.headerlen;
   memcpy(header2, l.buffer, header2len);	// copy original header
   goto SKIP;
  }
/**---------------------------------------------------------------------------*/
// local request processing

//                             "012345678901234"
  posterp = strstr(l.lines[0], "/sieve/filter/");
  if (posterp)
  {
   if (sievescore == 0.0)
    goto NOLOCAL;

   posterp += 14;
   posterendp = strchr(posterp, '/');
   if (!posterendp)
    goto NOLOCAL;

   *posterendp = 0;

   for (;;)
   {
    p = strchr(posterp, '%');
    if (!p)
     break;
    *p = ' ';
    p++;
    memcpy(p, p+2, posterendp - (p+2) + 1);
   }

   l.bodyp = l.buffer;
   p = l.bodyp;
   Append(&p, "<html>\r\n");
   Append(&p, "<head>\r\n");
   Append(&p, "<title>\r\n");
   Append(&p, "Sieve: A proxy server filter for the Scoop web log system\r\n");
   Append(&p, "</title>\r\n");
   Append(&p, "</head>\r\n");
   Append(&p, "<body>\r\n");
   Append(&p, "<h1>\r\n");
   Append(&p, "Sieve: A proxy server filter for the Scoop web log system\r\n");
   Append(&p, "</h1>\r\n");

   for (filter = 0; filter < nfilters; filter++)
   {
    filterp = &filters[filter];
    if (streq(filterp->name, posterp))
     break;
   }

   if (filter < nfilters)
    AppendF(&p, "Username \"%s\" is already listed in sieve.conf.\r\n", posterp);
   else
   {
    filep = fopen("sieve.conf", "a");		// append to end
    if (filep)
    {
     fprintf(filep, "%4.2f %s\n", sievescore, posterp);
     fclose(filep);
    }
    if (nfilters < NFILTERS)			// activate
    {
     filterp = &filters[nfilters];
     filterp->score = sievescore;
     strcpy(filterp->name, posterp);
     filterp->len = strlen(filterp->name);
     nfilters++;
    }
    AppendF(&p, "Username \"%s\" has been added to sieve.conf.\r\n", posterp);
   }

   Append (&p, "</body>\r\n");
   Append (&p, "</html>\r\n");

   l.bodylen = p - l.bodyp;

   versionp = posterendp + 2;	// .../sieve/filter/heurtley/ HTTP/1.1<null>
   p = header2;
   AppendF(&p, "%s 200 OK\r\n", versionp);
   Append (&p, "Content-type: text/html\r\n");
   AppendF(&p, "Content-length: %d\r\n", l.bodylen);
   Append (&p, "\r\n");
   header2len = p - header2;

   goto SNEAK;
  }

NOLOCAL:
/**---------------------------------------------------------------------------*/
// build modified header

  p = header2;

  for (line = 0; line < l.nlines; line++)
  {
   linep = l.lines[line];

//                  "123456789012"
   if (memeq(linep, "Keep-Alive: ", 12))	// nuke keep alive headers
    continue;
   if (memeq(linep, "Connection: ", 12))
    continue;

//                  "123456"
   if (memeq(linep, "Host: ", 6))
    AppendF(&p, "Host: %s", servername);	// replace Host:
   else
//                  "123456789"
   if (memeq(linep, "Referer: ", 9))		// restore Referer:
   {
    q = strstr(linep, "//");
    if (q)
    {
     q += 2;
     q = strchr(q, '/');
    }
    if (!q)
     Append(&p, linep);
    else
    {
     AppendF(&p, "Referer: http://%s", servername);
     Append(&p, q);				// append rest of original
    }
   }
   else
    Append(&p, linep);

   Append(&p, "\r\n");
  }

  header2len = p - header2;

SKIP:
/**---------------------------------------------------------------------------*/
// create network socket

  socketserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#ifdef WINDOWS
  if (socketserver == INVALID_SOCKET)
#else
  if (socketserver < 0)
#endif
  {
   rc = WSAGetLastError();
   printf("Unable to create socket: rc = %d.\n", rc);
   rv = 1;
   goto EGRESS;
  }

// connect to server

  rc = connect(socketserver, (struct sockaddr *)&sockaddrserver, sizeof(sockaddrserver));
#ifdef WINDOWS
  if (rc == SOCKET_ERROR)
#else
  if (rc)
#endif
  {
   rc = WSAGetLastError();
   printf("Unable to connect to web server: rc = %d.\n", rc);
   rv = 1;
   goto EGRESS;
  }
/**---------------------------------------------------------------------------*/
// send request to server in two parts

  rv = BigSend(socketserver, header2, header2len, &n);
  if (rv)
  {
   printf("Error while sending header to server.\n");
   rv = 1;
   goto EGRESS;
  }
  if (n != header2len)
  {
   printf("Error while sending header to server.\n");
   printf("Tried to send %d bytes, sent %d bytes.\n", header2len, n);
   rv = 1;
   goto EGRESS;
  }

  printf("%s %8d server xmit header\n", TimeStamp(), header2len);
  fflush(stdout);

  if (l.bodylen)
  {
   rv = BigSend(socketserver, l.bodyp, l.bodylen, &n);
   if (rv)
   {
    printf("Error while sending body to server.\n");
    rv = 1;
    goto EGRESS;
   }
   if (n != l.bodylen)
   {
    printf("Error while sending body to server.\n");
    printf("Tried to send %d bytes, sent %d bytes.\n", l.bodylen, n);
    rv = 1;
    goto EGRESS;
   }

   printf("%s %8d server xmit body\n", TimeStamp(), l.bodylen);
   fflush(stdout);
  }

#ifdef DEBUG
  filep = fopen("request.txt", "ab");
  fwrite("\r\n--------\r\n", 1, 2+8+2, filep);
  fwrite(header2, 1, header2len, filep);
  fwrite(l.bodyp, 1, l.bodylen, filep);
  fclose(filep);
#endif
/**---------------------------------------------------------------------------*/
// get reply from server

  rv = Receive(socketserver, "server", "reply.txt");
  if (rv)
  {
   printf("Error while receiving from server.\n");
   goto EGRESS;
  }

  closesocket(socketserver);
/**---------------------------------------------------------------------------*/
// massage reply body

  nposts = 0;
  nnuked = 0;

  bodyendp = &l.bodyp[l.bodylen];
  *bodyendp = 0;			// treat body like one big string
  scanp = l.bodyp;

  for (;;)
  {
   commentp = strstr(scanp, "<!-- start comment -->");
   if (!commentp)
    break;

   scanp = commentp + 1;

//                       "12345678901234567890"
   commentendp = strstr(commentp, "<!-- end comment -->");
   if (!commentendp)
    continue;
   commentendp += 20;

   posterp = strstr(commentp, ">by ");
   if (!posterp)
    continue;
   posterp += 4;

   posterendp = strstr(posterp, " on");
   if (!posterendp)
    continue;

   parenp = strstr(posterp, " (");
   if (parenp && parenp < posterendp)
    posterendp = parenp;

   for (; *posterendp == ' '; posterendp--);
   posterendp++;

   len = posterendp - posterp;

   for (filter = 0; filter < nfilters; filter++)
   {
    filterp = &filters[filter];
    if (filterp->len == len && memeq(posterp, filterp->name, filterp->len))    
     break;
   }

   if (filter >= nfilters)			// if no exact match
   {
    if (starfilterp)				// if catchall exists
     filterp = starfilterp;			// use it
    else					// otherwise
     filterp = 0;				// no filter
   }

   scorep = strstr(commentp, "showrate");
   if (!scorep)
    continue;
   scorep = strchr(scorep, '>');
   if (!scorep)
    continue;
   
   scorep++;					// point to score
   if (memeq(scorep, "none", 4))		// if no score yet
    score = nonescore;				// use nonescore
   else						// otherwise
    score = atof(scorep);			// get score

   if (filterp)					// if a filter is in effect
   {
    nposts++;					// count post

    if (score <= filterp->score)		// if low score
    {
     memcpy					// nuke post, keep null
     (
      commentp,
      commentendp,
      bodyendp - commentendp + 1
     );
     len = commentendp - commentp;
     l.bodylen -= len;				// adjust body length
     bodyendp -= len;				// adjust body endp
     nnuked++;					// count nuked post
     continue;					// test next post
    }
   }

   if						// if
   (
    filter >= nfilters				// no exact match
    &&						// and
    sievescore != 0.0				// sievescore is defined
    &&						// and
    score <= sievescore				// post would have been nuked
   )
   {
    *posterendp = 0;				// delimit posterp
    sprintf
    (
     filterlink,
     " (<a href=\"http://%s:%d/sieve/filter/%s/\">sieve</a>)",
     hostname,
     port,
     posterp
    );
    *posterendp = ' ';				// restore char

    len = strlen(filterlink);
    memmove					// make room, keep null
    (
     posterendp + len,
     posterendp,
     bodyendp - posterendp + 1
    );
    l.bodylen += len;				// adjust body length
    bodyendp += len;				// adjust body endp
    memcpy(posterendp, filterlink, len);	// insert filter link
   }

  }						// for (;;)

  if (nposts)
   printf("%s %8d posts tested, %d removed\n", TimeStamp(), nposts, nnuked);
/**---------------------------------------------------------------------------*/
// build modified header

  p = header2;

  for (line = 0; line < l.nlines; line++)
  {
   linep = l.lines[line];
//                  "1234567890123456"
   if (memeq(linep, "Content-Length: ", 16))
    AppendF(&p, "Content-Length: %d", l.bodylen);
   else
//                  "123456789012"
   if (memeq(linep, "Set-Cookie: ", 12))
   {
//                    "1234567"
    q = strstr(linep, "domain=");
    if (!q)
     Append(&p, linep);
    else
    {
     q += 7;
     *q = 0;
     Append(&p, linep);
     Append(&p, hostname);
    }
   }
   else
    Append(&p, linep);

   Append(&p, "\r\n");
  }

  header2len = p - header2;
/**---------------------------------------------------------------------------*/
// send reply to browser in two parts

SNEAK:

  rv = BigSend(socketbrowser, header2, header2len, &n);
  if (rv)
  {
   printf("Error while sending header to browser.\n");
   rv = 1;
   goto EGRESS;
  }
  if (n != header2len)
  {
   printf("Error while sending header to browser.\n");
   printf("Tried to send %d bytes, sent %d bytes.\n", header2len, n);
   rv = 1;
   goto EGRESS;
  }

  printf("%s %8d browser xmit header\n", TimeStamp(), header2len);
  fflush(stdout);

  if (l.bodylen)
  {
   rv = BigSend(socketbrowser, l.bodyp, l.bodylen, &n);
   if (rv)
   {
    printf("Error while sending body to browser.\n");
    rv = 1;
    goto EGRESS;
   }
   if (n != l.bodylen)
   {
    printf("Error while sending body to browser.\n");
    printf("Tried to send %d bytes, sent %d bytes.\n", l.bodylen, n);
    rv = 1;
    goto EGRESS;
   }

   printf("%s %8d browser xmit body\n", TimeStamp(), l.bodylen);
   fflush(stdout);
  }

#ifdef DEBUG
  filep = fopen("reply.txt", "ab");
  fwrite("\r\n--------\r\n", 1, 2+8+2, filep);
  fwrite(header2, 1, header2len, filep);
  fwrite(l.bodyp, 1, l.bodylen, filep);
  fclose(filep);
#endif
/**---------------------------------------------------------------------------*/ 
  closesocket(socketbrowser);

 }				// for (;;) listen loop

EGRESS:

 closesocket(socketbrowser);
 closesocket(socketlisten);
 closesocket(socketserver);

// deinitialize networking

#ifdef WINDOWS

 if (wsadata.wVersion)
  WSACleanup();

#endif

 return(rv);
}
