/* ZPROX 0.3.0
 * Proxy with compression support
 *     by Willy Tarreau
 *
 * zprox       This program was designated to speed up modem connexions by
 *             compressing data. Its main advantage is that you can choose
 *             the compression strength for each one of the two ways of
 *             communication. I made this because HTTP headers don't compress
 *             well, nor does telnet input, which would be slowed down and
 *             even generate network overhead. Of course, by disabling the
 *             compression on each way, it acts like a simple proxy.
 *             Personal advice: use it between two squid proxies, you'll
 *             get about 15 kB/s with a 33600 modem on HTML pages.
 *
 * IMPORTANT:  ZPROX needs libz.a and zlib.h (zlib 1.0.4 or 1.1.2) to compile.
 *             This software is still alpha. It is possible that
 *             something goes wrong. It shouldn't come from zlib which
 *             is known to work at this time.
 *             See zlib home page at http://quest.jpl.nasa.gov/zlib/
 *
 * $Id: zprox.c,v 0.3 1998/06/08 9:44:15 willy Exp $
 *
 * Author:
 *            - Willy Tarreau <tarreau@aemiaif.lip6.fr>
 *
 * Copying-policy: Gnu Public Licence - Freeware
 *
 * ZPROX is under the GPL. You should have received a copy of this licence.
 * If not, please retrieve it from the FSF and read it.
 *
 * The compression algorithms used here are directly taken from
 *
 * ZPROX IS DISTRIBUTED WITH NO EXPRESSED OR IMPLIED SORT OF WARANTY.
 * THE AUTHORS SHOULD NOT BE LIABLE FOR ANY DAMAGE OR LOSS CAUSED TO
 * YOU, YOUR SYSTEM OR ANYBODY.
 *
 *
 */

#include <stdio.h>
#ifndef NO_STDLIB
#include <stdlib.h>
#endif
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <setjmp.h>
#include <sys/socket.h>
#include "zlib.h"

/* this is for gcc for win32 for those who want to test ... */
#ifdef WIN32

#ifndef EINPROGRESS
#define EINPROGRESS 10036
#endif

#ifndef SO_ERROR
#define SO_ERROR 0x1007
#endif

#endif

/* user configurable compilation-time parameters */
#define NBDESC		1024
#define BUFSIZE		16384
#define LOCALPORT	3125
#define REMOTEPORT	3128
#define INACTION	ACT_COPY
#define OUTACTION	ACT_COPY
#define GZOLEVEL	6
#define GZILEVEL	6
#define CHNBFREE	4

/* upper limit beyond which we stop receiving. */
#define RHIWAT		(bufsize*7/8)
/* lower limit below which we stop sending.
   Important: to avoid deadlocks, WLOWAT must be lower than
   bufsize-max(minfreeout).
*/
#define WLOWAT		(bufsize/4)

/* Here are some state constants. A socket state is a logical OR of these flags */
#define NOTUSED	0x00
#define	RSHUT	0x01
#define	WSHUT	0x02
#define	RWSHUT	( RSHUT | WSHUT )
#define NOTYET	0x04
#define	CONN	0x08

/* consts that define the operations on a one-direction channel */
#define	ACT_COPY	0x00
#define ACT_DEFLATE	0x01
#define ACT_INFLATE	0x02
#define NB_ACTIONS      0x03

typedef struct sbuf tbuf;
typedef struct sch tch;
typedef struct sdesc tdesc;


/* buffer description */
struct sbuf {
  unsigned int r, w, l;	/* read ptr, write ptr, data length */
  char data[1];
};

/* let's define a channel structure */
struct sch {
  unsigned int mode;	  /* ACT_COPY/ACT_DEFLATE/ACT_INFLATE ... */
  z_stream strm; /* for inflate/deflate */
  char end;      /* 1 = "if RB doesn't change, no more bytes will go to WB" */
  int rd, wd;   /* read desc and write desc */
  tbuf *rb, *wb; /* read buffer and write buffer */
  tch *next; /* next one in free list */
};

/* now a descriptor structure */
struct sdesc {
  unsigned int st;	/* socket state */
  int oth;    /* the descriptor on the other end */
  tch	*r,*w;	/* Read and Write channels */
};

/***************** some typed constants ****************/

/* This defines, for each possible action to be performed, the minimum
   amount of free bytes in the write buffer.
*/
int minfreeout[NB_ACTIONS]={0,    /* ACT_COPY*/
                            256,  /* ACT_DEFLATE */
                            256   /* ACT_INFLATE */
                           };

/******************** now the variables ****************/
int nbdesc	= NBDESC;
int bufsize	= BUFSIZE;
int inaction	= INACTION;
int outaction	= OUTACTION;
int gzolevel	= GZOLEVEL;
int gzilevel	= GZILEVEL;

/* used for statistics */
unsigned long int glob_in=0, glob_out=0;

/* memory pool for inflate/deflate */
char *gztmpi, *gztmpo;

/* the descriptors entry point. desc[0..2]=stdin..stderr, desc[3]...=sockets */
tdesc *desc;
fd_set *rfd, *wfd;
int highdesc;

/* the listening socket */
int listensock;

/* adresse de connexion distante */
char *remotehost=NULL;
int   remoteport=REMOTEPORT;
struct sockaddr_in remote;

int   localport=LOCALPORT;
struct sockaddr_in local;

int chnormfree=16;	/* min of free channels in memory */
int chnbfree=0;		/* actual number of free channels. */
tch *freech;	/* first free channel in memory */


/* handles stops to terminate normally */
void sighandler(int sig) {
  exit(0);
}


/* disables a one-way or two-way communication on a socket descriptor, and
   tags it as "shutdown" in the descriptors table.
   The mode parameter should be any logical OR based on these values:
   - RSHUT
   - WSHUT
   - RWSHUT
*/
void shutdesc(int d, int mode) {

  if (mode&RSHUT) {
    /*fprintf(stderr,"Shutdown READ:  %d\n",d);*/
    shutdown(d,0);
  }
  if (mode&WSHUT) {
    /*fprintf(stderr,"Shutdown WRITE: %d\n",d);*/
    shutdown(d,1);
  }
  if (d<nbdesc)
    desc[d].st |= mode;

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

}

/* Closes a descriptor and marks it unused in the table.
*/
void closedesc(int d) {

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

  close(d);
  /*fprintf(stderr,"socket %d closed.\n",d);*/
  if (d<nbdesc)
    desc[d].st=NOTUSED;


  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

}




/* returns a pointer onto a new freshly initialized channel.
   Warning: these fields remain undefined and must be set by the caller:
   	  rd, wd, mode.
   The zstream still needs to be partially initialized by a deflateInit() or
   inflateInit().
 */
tch *chcreate() {
	tch *ch;

	ch=(tch*)malloc(sizeof(tch));
	ch->end=1;
	ch->strm.zalloc = NULL;
	ch->strm.zfree  = NULL;
	ch->strm.opaque = NULL;

	/* 2 fresh buffers, as needed.
       There's no need to initialize the data field of the buffer because
	   it will be overwritten before being read.
	 */
	bzero(ch->rb=(tbuf*)malloc(sizeof(tbuf)+bufsize),sizeof(tbuf));
	bzero(ch->wb=(tbuf*)malloc(sizeof(tbuf)+bufsize),sizeof(tbuf));

	/* let's initialize the stream pointers to point to the buffers.
   	   Normally, this shouldn't have to be done because these fields
	   will be filled by transfer_data().
	 */
	ch->strm.next_in = ch->rb->data;
	ch->strm.avail_in = 0;			/* empty input stream buffer */
	ch->strm.next_out = ch->wb->data;
    ch->strm.avail_out = bufsize;	/* empty output stream buffer */
	ch->next=freech;	/* prepares the chaining of free channels */
	chnbfree++;	/* one new free channel */
	return ch;
}


/*
 returns a pointer onto a free and initialized channel.
 */
tch *challoc() {
	tch *ch;
	if (!freech) {  /* no more free channels */
		freech=chcreate(); /* let's create a new one */
		/*fprintf(stderr,"allocation et creation d'1 canal.\n");*/
	}
	else {
		/*fprintf(stderr,"allocation d'1 canal disponible.\n");*/
	}
	ch=freech;
	freech=ch->next;
	chnbfree--;
	/*fprintf(stderr,"   %d canaux libres.\n",chnbfree);*/
	return ch;
}


/* the channel ch is restored to the list of free channels.
   its buffers remain allocated, but re-initialized. There should be no
   difference between this channel and a new preallocated one.
 */
void chfree(tch *ch) {
	ch->end=1;
	ch->next=freech;
	bzero(ch->rb,sizeof(tbuf));
	bzero(ch->wb,sizeof(tbuf));
	freech=ch;
	chnbfree++;

	/*fprintf(stderr,"1 canal libere. %d libres\n",chnbfree);*/
}

/* kills n free channels from the free list. */
void chkill(int n) {
	tch *next;

	/*fprintf(stderr,"destruction de %d canaux.\n",n);*/

	while (n && freech) {
		free(freech->rb);
		free(freech->wb);
		next=freech->next;
		free(freech);
		freech=next;
		n--; chnbfree--;
	}
	/* tries to detect eventual errors */
	if (!freech) {
		if (chnbfree) {
			fprintf(stderr,"INTERNAL ERROR: freech=NULL, chnbfree=%d. CLEARED.\n",chnbfree);
			chnbfree=0;
		}
	}
	else {
		if (chnbfree<=0) {
			fprintf(stderr,"INTERNAL ERROR: freech!=NULL, chnbfree=%d. CLEARED.\n",chnbfree);
			chnbfree=0;
			while (freech)
				freech=freech->next;
		}
	}
}


/* destroys a communication channel, i.e. it shuts down the read on the read
   descriptor, and the write on the write descriptor. It then frees the
   buffers, eventually ZLIB streams, and the channel descriptor itself. Any
   reference to it in the descriptor table is removed (set to NULL), and if
   its associated channel in this connexion has already been destroyed, then
   the socket is released. NOTE That any data is cleared from the buffers and
   lost. It's to the caller to make sure there was nothing important to keep.
*/
void destroy_channel(tch *ch) {
  int d1,d2;

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

  /* shut sockets down */
  shutdesc(d1=ch->rd, RSHUT);
  shutdesc(d2=ch->wd, WSHUT);

#if 0
  /* free buffers */
  if (ch->rb) free(ch->rb);
  if (ch->wb) free(ch->wb);

#endif
  /* free ZLIB streams */
  if (ch->mode==ACT_DEFLATE) {
    long unsigned int i,o;
    /*fprintf(stderr,"Def.Stat: %lu in,%lu out,%%out=%d,g_in=%lu,g_out=%lu,g%%=%d\n",
	    i=ch->strm.total_in,
	    o=ch->strm.total_out,
	    (int)(i?(o*100/i):1),
	    (glob_in+=i), (glob_out+=o),
	    (int)(glob_in?(glob_out*100/glob_in):1));
	*/
    deflateEnd(&ch->strm);
  }
  else if (ch->mode==ACT_INFLATE) {
    /*fprintf(stderr,"Stats for inflate: %lu in, %lu out\n",
	    ch->strm.total_in,ch->strm.total_out);
	*/
    inflateEnd(&ch->strm);
  }

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

#if 0
  free(ch);
#endif

  chfree(ch);

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

  desc[d1].r=NULL; desc[d2].w=NULL; /* releases the pointers */
  if (desc[d1].w==NULL) {	/* the other channel was already closed. */
    closedesc(d1);	/* so we can safely close the sockets */
    closedesc(d2);
  }

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

}


/* destroys the 2 channels attached to this descriptor if they still exist,
   the descriptor which is on the other end, and then force a close on the
   descriptor to ensure it's correctly freed.
*/
void destroy_desc(int d) {

  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

  if (desc[d].r) destroy_channel(desc[d].r);
  if (desc[d].w) destroy_channel(desc[d].w);
  closedesc(d);
  if (desc[d].oth>0)
    closedesc(d);
  /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

}

/* displays a fatal error message and exists.
*/
void fatal(char *msg) {
  fprintf(stderr,"FATAL ERROR -> %s",msg);
  perror(" <- ");
  exit(1);
}

/* this function initializes all the data needed to power the proxy up
*/
void init() {
  struct hostent *he;
  int i;

  signal(SIGINT,sighandler);
  signal(SIGQUIT,sighandler);
  signal(SIGPIPE, SIG_IGN);

  /* initialize the descriptors table */
  desc=(tdesc*)malloc(sizeof(tdesc)*nbdesc);
  rfd=(fd_set*)malloc(sizeof(fd_set)*nbdesc/FD_SETSIZE);
  wfd=(fd_set*)malloc(sizeof(fd_set)*nbdesc/FD_SETSIZE);
  if ((desc==NULL) || (rfd==NULL) || (wfd==NULL)) fatal("malloc() for desc table");

  if ((gztmpi=(char *)malloc(bufsize))==NULL) fatal("malloc() for gztmpi");
  if ((gztmpo=(char *)malloc(bufsize))==NULL) fatal("malloc() for gztmpo");

  for (i=0; i<nbdesc; i++)
    desc[i].st=NOTUSED;
  highdesc=0;	/* higher descriptor used +1 = first one not used */


  bzero(&remote,sizeof(struct sockaddr_in));
  remote.sin_family=AF_INET;
  remote.sin_port=htons(remoteport);

  if((he=gethostbyname(remotehost))!=NULL)
    bcopy(he->h_addr, &remote.sin_addr, he->h_length);
  else if((remote.sin_addr.s_addr=inet_addr(remotehost))==-1)
    fatal("gethostbyname()");

  /* let's create the listen socket */


  if ((listensock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==-1)
    fatal("socket()");

  i=1;
  if ((setsockopt(listensock,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) == -1)
    fatal("setsockopt()");

  bzero((char *)&local, sizeof(local));
  local.sin_family=AF_INET;
  local.sin_addr.s_addr=htonl(INADDR_ANY);
  local.sin_port=htons(localport);
  if (bind(listensock,(struct sockaddr *)&local,sizeof(local))==-1)
    fatal("bind()");
  if (listen(listensock,5)==-1)
    fatal("listen()");

  /* now there's a server listening on the local port */
}

/* this function transfers data from the read buffer to the write buffer,
   through a filter which can be at the moment:
   - COMPRESS   (when ch->mode==ACT_DEFLATE)
   - DECOMPRESS (when ch->mode==ACT_INFLATE)
   - a simple copy (when ch->mode==ACT_COPY)

   returns 1 if something has been done, 0 else.
*/
int transfer_data(tch *ch) {
  tbuf *r, *w;
  int ldata, freespace, status;
  char altbufi, altbufo;

  /* As we work with contiguous blocks of data, we'll sometimes have to
     copy parts of buffers because buffers are linear modulo their size
     and the first byte follows the last one.
     So we'll have to determine what maximum amount of data can be taken from
     the read buffer, and what maximum can be written to the write buffer.

     The stream buffer will be set up to show only the beginning of the source
     block, its size, the beginning of the dest block and its size.
     INFLATE/DEFLATE accomodate very well this way. When using single copy,
	 either we only copy the minimum common block, or we can do a max-3-steps
	 copy, or even a byte-by-byte (slow but far simpler).
     */

  if (((ch->end) && (ch->rb->l==0)) ||
      (bufsize-ch->wb->l<minfreeout[ch->mode]))
    /* if there's nothing to do or nowhere to store data, we return.
       This assumption is made for the remainder of the function. If
       it had to be removed, then there would be some tests to be
       done on empty/full buffers.
       */
    return 0;

  r=ch->rb; w=ch->wb;

  /************************ simple copy mode ************************/
  /* in this mode of operation, this proxy acts like a standard one.*/
  /* this mode is treated apart for speed considerations.           */
  /******************************************************************/

  if (ch->mode==ACT_COPY) {	/* simple copy mode */
    int nb, nbtot=0; /* number of bytes to copy at a time */

    while ((r->l) && (w->l<bufsize)) {
      /* 1 - source block length */
      if (r->l > 0) {
	if (r->w > r->r)
	  ldata = r->w - r->r;
	else
	  ldata = bufsize - r->r;
      }
      else break; /* nothing more to be read */
      /* 2 - dest space */
      if (w->r > w->w)
	freespace = bufsize - w->l;
      else if (r!=w) /* if r==w, we know the buffer is empty and not full */
	freespace = bufsize - w->w;
      else break; /* buffer full */

      if (freespace<ldata)
	nb=freespace;
      else
	nb=ldata;

      bcopy(&r->data[r->r], &w->data[w->w], nb);
      r->r=(r->r+nb)%bufsize; r->l-=nb;
      w->w=(w->w+nb)%bufsize; w->l+=nb;
      nbtot+=nb;
    }
    /* resets the 2 pointers to the beginning if the buffer is empty */
    if (!r->l)
      r->r=r->w=0;
    ch->end = 1;
    return (nbtot>0);
  }



  /************************ compression mode *************************/
  /* deflate() should not be called too often, or it will reduce the */
  /* global compression ratio. For this reason, we'll try to call it */
  /* with the maximum amount of data in the read buffer.             */
  /*******************************************************************/

  /* 1 - source block length - If the read buffer (RB) is fragmented,
     we'll temporarily unfragment it to a temp buffer, and work with
     this one. */

  altbufi=0;
  if (r->l > 0) { /* there are still data. */
    if (r->r < r->w) { /* as the buffer is contiguous, we'll work on it */
      ch->strm.next_in  = &r->data[r->r];
    }
    else { /* the buffer is fragmented. We'll work on a copy of it */
      bcopy(&r->data[r->r], gztmpi, bufsize - r->r);  /* the first block */
      bcopy(&r->data, &gztmpi[bufsize - r->r], r->w); /* second block */
      ch->strm.next_in  = gztmpi;
      altbufi=1;
    }
  }
  else ;  /* nothing to be read, but perhaps some more data to extract
             from deflate/inflate's internal buffers. */
  /* input data size */
  ch->strm.avail_in = r->l;

  /* 2 - dest space: for the same reasons, we need a contiguous buffer.
     The first thing we'll do will be to prepare another buffer if this
     one's not enough. */
  altbufo=0;
  if ((w->w < w->r) || ((w->w|w->r)==0)){
    ch->strm.next_out = &w->data[w->w];  /* write pointer inside the buffer */
  }
  else { /* must use an alternate buffer */
    ch->strm.next_out = gztmpo;
    altbufo=1;
  }
  /* output buffer space */
  ch->strm.avail_out = bufsize - w->l;


  /* note that EVEN if there is no source data available, we MUST proceed
     with deflate() or inflate() since they will go on from their internal
     buffers.
  */
  if (ch->mode==ACT_DEFLATE || ch->mode==ACT_INFLATE) {
    int outb;
    /*fprintf(stderr,"1) ch->rd=%d, ch->wd=%d\n" \
      "   ni=%08X, ai=%d, ti=%d, no=%08X, ao=%d, to=%d\n"\
      "   rr=%d, rw=%d, rl=%d, ldata=%d,\n"\
      "   wr=%d, ww=%d, wl=%d, freesp=%d\n",
      ch->rd, ch->wd,
      ch->strm.next_in, ch->strm.avail_in, ch->strm.total_in,
      ch->strm.next_out, ch->strm.avail_out, ch->strm.total_out,
      r->r, r->w, r->l, ldata,
      w->r, w->w, w->l, freespace);
      */
    if (ch->mode==ACT_DEFLATE) {	/* Deflate */
      status=deflate(&ch->strm, Z_PARTIAL_FLUSH);
      if (status==Z_BUF_ERROR) { /* no progress was made */
	fprintf(stderr,"************deflate: Z_BUF_ERROR\n");
	ch->end=1;  /* nothing can be done until input is changed */
	return 0;  /* nothing done */
      }
      else if (status!=Z_OK) {
	fprintf(stderr,"************Deflate Error %d\n",status);
	ch->end=1;
	r->l=0; r->w=r->r; /* free the buffer as we won't be able to re-read it further */
	shutdesc(ch->rd,RSHUT);
	return 0;
      }
      ch->end = (ch->strm.avail_out>0);
    }
    else { /* Inflate */
      status=inflate(&ch->strm, Z_PARTIAL_FLUSH);
      if (status!=Z_OK/*==Z_BUF_ERROR*/) { /* means end of file for gunzip. */
	fprintf(stderr,"***************inflate warning: end of file ? status=%d\n",status);
	ch->end=1;
	r->l=0; r->w=r->r; /* free the buffer as we won't be able to re-read it further */
	shutdesc(ch->rd,RSHUT);
	return 1;
      }
      else /* finished by default */
	ch->end=1;
    }
    /*fprintf(stderr,"2) status=%d, next_in=%08X, avl_in=%d, next_out=%08X, avl_out=%d\n",
      status,
      ch->strm.next_in, ch->strm.avail_in,
      ch->strm.next_out, ch->strm.avail_out);*/

    /* now it's time to evaluate how much the read buffer has progressed. */

    if (r->l > 0) { /* should anything have been done ? */
      if (!altbufi) { /* no auxiliary buffer used */
	r->r = ( r->r + r->l - ch->strm.avail_in ) % bufsize;
	r->l = ch->strm.avail_in;
      }
      else {  /* gztmpi was used here. Let's copy the remainder back onto RB.*/
	bcopy(gztmpi+(r->l-ch->strm.avail_in), &r->data[r->r=0],
	      r->w=r->l=ch->strm.avail_in);
      }
    }
    /* if there's nothing left to read, we reset the pointers to the
       beginning of the buffer to speed the next access up.
       */
    if (r->l==0) r->r=r->w=0;

    /* now restitute the data to the WB. */

    outb = bufsize - w->l - ch->strm.avail_out;  /* outb=bytes generated */
    if (altbufo) { /* the buffer was fragmented, so use gztmpo. */
      int fs = bufsize - w->w;

      bcopy(gztmpo, &w->data[w->w], (outb<fs)?outb:fs);
      if (outb>fs) { /* copy the second block if needed */
	bcopy(&gztmpo[fs], w->data, outb-fs);
      }
    }
    w->w = ( w->w + outb ) % bufsize;
    w->l += outb;

    /*fprintf(stderr,"out: w->r=%d, w->w=%d, w->l=%d, outb=%d\n",
	    w->r, w->w, w->l, outb);*/
    return (outb>0);
  }  /* end of GZIP / GUNZIP modes */
  fprintf(stderr,"*****************ligne %d: unreachable\n",__LINE__);
  return 0;
}


/* the select loop itself !
*/
void select_loop() {
  int s;	/* socket descriptor */
  tdesc *d;
  int ss;
  jmp_buf jmpbuf;
  int newhighdesc;	/* used for socket creations */
  int activity;

  /* here it is (the select loop) */
  /* WARNING: The longjmp's are very important because once a channel has
     been deleted, we can't rely on some sockets nor on the FD_ISSET
     behavior because some closed sockets may still be activated. So the
     better way is to agree that channel destruction is not that so often
     and so we can go back to the beginning of the loop each time we delete
     one.

     The main problem is that gcc sometimes forgets some local variables
     that are affected before a longjmp if they aren't referenced farther
     in the program.

     Perhaps a future release will be more precise with only FD_SET/FD_CLR
     manipulations.
     */
  newhighdesc=highdesc;

  if (setjmp(jmpbuf));
  while (1) {


    /*fprintf(stderr,"highdesc=%d, Ligne %d\n",highdesc,__LINE__);*/
    highdesc=newhighdesc;
    /*fprintf(stderr,"highdesc=%d, Ligne %d\n",highdesc,__LINE__);*/

    /* first, we'll process internal jobs (cleanup, compression...) */
    do {
      activity=0;
      FD_ZERO(rfd); FD_ZERO(wfd);

      /* let's see which descriptor can be read and which one can be written */
      for (s=highdesc-1; s>=0; s--) {
	/*printf("desc=%d, st=0x%02X\n",s,desc[s].st);*/
	if ((d=&desc[s])->st==NOTUSED) {
	  if (s==highdesc-1) highdesc=s; /* automagically updates highdesc */
	  continue;
	}

	/*fprintf(stderr,"Ligne %d\n",__LINE__);*/


	/****************************************************************/
	/* First, we look for deletable channels. The simplest ones simply
	   have a shut down output port, which of course is completely
	   useless.
	   *****************************************************************/
	if ((d->st&WSHUT) && (d->w!=NULL)) {
	  activity=1;
	  destroy_channel(d->w);
	}

	/*fprintf(stderr,"Ligne %d\n",__LINE__);*/

	/* the second type are those whose input is closed without any
	   data left in the buffers nor in any internal mechanism.
	   No new data will never go to the output so we also close.
	   */
	if ((d->st&RSHUT) && (d->r!=NULL)) {
	  if ((d->r->rb->l==0) &&	/* no data in read buffer */
	      (d->r->wb->l==0) &&	/* no data in write buffer */
	      (d->r->end)) { /* data in stream out already flushed */
	    /* kill the channel as it's useless now. */
	    activity=1;
	    destroy_channel(d->r);
	  }
	}


	/*fprintf(stderr,"Ligne %d\n",__LINE__);*/

	if ((d->w!=NULL) && (d->w->rb->l>0) && (d->w->wb->l<bufsize)) {
	  /* some data can be transfered from RB to WB. As we use
	     Hi Water/Lo water method to reduce network overhead,
	     there is a potential risk that data from RB will never be
	     transfered to WB because it would be under the low water
	     mark. Thus, we'll make this transfer by hand now.
	     */
	  /*fprintf(stderr,"ligne %d: r->l=%d, w->l=%d, end=%d\n",
	    __LINE__, d->w->rb->l, d->w->wb->l, d->w->end);*/
	  activity |= transfer_data(d->w);
	  /*fprintf(stderr,"ligne %d: r->l=%d, w->l=%d, end=%d\n",
	    __LINE__, d->w->rb->l, d->w->wb->l, d->w->end);*/
	}


	/*fprintf(stderr,"Ligne %d\n",__LINE__);*/
      }
      /*fprintf(stderr,"activity=%d\n",activity);*/
    } while (activity);

    /* let's see which descriptor can be read and which one can be written */
    for (s=highdesc-1; s>=0; s--) {
      /*printf("desc=%d, st=0x%02X\n",s,desc[s].st);*/
      if ((d=&desc[s])->st==NOTUSED) {
	if (s==highdesc-1) highdesc=s; /* automagically updates highdesc */
	continue;
      }


      /****************************************************************/
      /* Now, let's manage activation on read ports                   */
      /****************************************************************/
      if ((d->r!=NULL) && (!(d->st&RSHUT)) && (d->r->rb->l<=RHIWAT) && !(d->st&NOTYET)) {
	FD_SET(s,rfd);
	/*fprintf(stderr,"%d active en READ\n",s);*/
      }


      /*    fprintf(stderr,"Ligne %d\n",__LINE__);*/

      /****************************************************************/
      /* Ok, activation on write ports                                */
      /****************************************************************/
      if ((d->w!=NULL) &&
	  ((d->st&NOTYET) ||/* connection not active yet */
	   (d->w->wb->l>WLOWAT) || /* or write buffer growing quickly */
	   ((d->w->wb->l>0) && /* or a few data in the buffer, and */
	    (d->w->rb->l==0) && /* no data in rbuf */
	    (d->w->end)))) { /* no more data to append to it */
	FD_SET(s,wfd); /* we activate it */
	/*fprintf(stderr,"%d active en WRITE\n",s);*/
      }
    }

    /* if there are enough free descriptors to allow new connections, then we do.*/
    if (highdesc<=nbdesc-2) {
      FD_SET(listensock,rfd);
      if (listensock>=highdesc) highdesc=listensock+1;
    }

    /* now, the first part of the job is done ! */

    /*fprintf(stderr,"highdesc=%d, Ligne %d\n",highdesc,__LINE__);*/

    /*	sleep(1);*/

	/*
	 if the number of free buffer is not equal to that required +/- 1,
	 we can adjust it now.
	*/

	if (chnbfree>chnormfree+1)
		chkill(chnbfree-(chnormfree+1));
	else {
		while (chnbfree<chnormfree)
			freech=chcreate();
	}

    newhighdesc=highdesc;
    do {
      ss=select(highdesc, rfd, wfd, NULL, NULL);	/* NULL timeout for the moment. */
    } while ((ss==-1) && (errno==EINTR));

    /*fprintf(stderr,"select=%d\n",ss);*/

    if (ss==-1) {	/* should never appear */
      perror("select()");
      continue;	/* retry the loop */
    }


    /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

    /* since the timeout is NULL, ss must be *at least* 1 */
    if (FD_ISSET(listensock,rfd)) {
      /* we have to accept a new connection */
      struct sockaddr saddr;
      int sal=sizeof(saddr);
      int sockout, lowat;
      struct linger lin;


      /*    fprintf(stderr,"Ligne %d\n",__LINE__);*/

      s=accept(listensock,&saddr,&sal);
      lin.l_onoff=1;
      lin.l_linger=10;
      if ((setsockopt(s,SOL_SOCKET,SO_LINGER,&lin,sizeof(lin))) == -1)
	perror("Warning: setsockopt(SO_LINGER)");
      lowat=8192;
      /*if ((setsockopt(s,SOL_SOCKET,SO_RCVLOWAT,&lowat,sizeof(lowat))) == -1)
	perror("Warning: setsockopt(SO_RCVLOWAT)");*/
      if ((sockout=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
	perror("socket()");
	closedesc(s);
	/*longjmp(jmpbuf,1);*/
	continue;
      }
      lin.l_onoff=1;
      lin.l_linger=10;
      if ((setsockopt(sockout,SOL_SOCKET,SO_LINGER,&lin,sizeof(lin))) == -1)
	perror("Warning: setsockopt(SO_LINGER)");
      if (fcntl(s,F_SETFL,O_NONBLOCK)==-1) {
	perror("Warning: fcntl(SET O_NONBLOCK,s_in)");
      }
      if (fcntl(sockout,F_SETFL,O_NONBLOCK)==-1) {
	perror("Warning: fcntl(SET O_NONBLOCK,s_out)");
      }
      if (connect(sockout,(struct sockaddr*)&remote,sizeof(remote))==-1) {
	if (errno!=EINPROGRESS) {
	  perror("connect()");
	  destroy_desc(s); destroy_desc(sockout);
	  /*longjmp(jmpbuf,1);*/
	  continue;
	}
	desc[sockout].st=NOTYET;
	/*fprintf(stderr,"Connexion asynchrone sur socket %d pour %d\n",sockout,s);*/
      }
      else {
	desc[sockout].st=CONN;
	/*fprintf(stderr,"Connexion immediate sur socket %d pour %d\n",sockout,s);*/
      }
      desc[s].st=CONN;
      desc[s].oth=sockout; desc[sockout].oth=s;
      desc[s].r=desc[sockout].w=challoc();
      desc[s].w=desc[sockout].r=challoc();
      desc[s].r->rd=s; desc[s].r->wd=sockout;
      desc[s].w->rd=sockout; desc[s].w->wd=s;
      desc[s].r->mode=outaction; desc[s].w->mode=inaction;

      /* Let's prepare zlib */
      if (outaction==ACT_DEFLATE)
		deflateInit(&desc[s].r->strm, gzolevel);
      else if (outaction==ACT_INFLATE)
		inflateInit(&desc[s].r->strm);
      if (inaction==ACT_DEFLATE)
		deflateInit(&desc[s].w->strm, gzilevel);
      else if (inaction==ACT_INFLATE)
		inflateInit(&desc[s].w->strm);

#if 0
      /* let's initialize the stream pointers to point to the buffers */
      desc[s].r->strm.next_in = desc[s].r->rb->data;
      desc[s].r->strm.avail_in = 0;		/* empty input stream buffer */
      desc[s].r->strm.next_out = desc[s].r->wb->data;
      desc[s].r->strm.avail_out = bufsize;	/* empty output stream buffer */

      desc[s].w->strm.next_in = desc[s].w->rb->data;
      desc[s].w->strm.avail_in = 0;		/* empty input stream buffer */
      desc[s].w->strm.next_out = desc[s].w->wb->data;
      desc[s].w->strm.avail_out = bufsize;	/* empty output stream buffer */
#endif

      if (s>=newhighdesc) newhighdesc=s+1;
      if (sockout>=newhighdesc) newhighdesc=sockout+1;

      /*ss--;	/* counts one socket less */
      /* disables these sockets for this turn */
      /*FD_CLR(s,rfd); FD_CLR(sockout,rfd);
	FD_CLR(s,wfd); FD_CLR(sockout,wfd);*/

      /*fprintf(stderr,"highdesc=%d newhighdesc=%d\n",highdesc, newhighdesc);*/
      /*fprintf(stderr,"Creation: sockout=%d, s=%d, rr=%d, rw=%d, wr=%d, ww=%d\n",sockout,s,
	desc[s].r->rb->l,desc[s].r->wb->l,
	desc[s].w->rb->l,desc[s].w->wb->l);*/
      continue;  /* highdesc changed. we can't trust other FD_ISSET() */
      /* FD_CLR(listensock,wfd); FD_CLR(listensock,rfd);*/
    }


    /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

    /* We'll look at the write descriptors first, in order to quickly
       output data to free the place to new ones. */
    for (s=0;s<highdesc;) {
      /* this test allows to skip the empty descriptors block by block */
#if 0
      if (!((long int*)wfd)[s/FD_SETSIZE]) {
	s=(s%FD_SETSIZE)+FD_SETSIZE;
	continue;
      }
#endif
      if (FD_ISSET(s,wfd)) {


	/*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, s, desc[s].w->wb->r);*/

	if (desc[s].st&NOTYET) {  /* it's the ack from an async connect */
	  int retval, lretval=sizeof(retval);
#ifndef WIN32
	  getsockopt(s,SOL_SOCKET,SO_ERROR,&retval,&lretval);
#else
	  retval=1;
#endif
	  if (retval) {
	    /*fprintf(stderr,"Socket %d: code retour connect=%d. Fermeture.\n",
	      s,retval);*/
	    FD_CLR(s,wfd); FD_CLR(s,rfd);
	    if (desc[s].oth>0) {
	      FD_CLR(desc[s].oth,wfd); FD_CLR(desc[s].oth,rfd);
	    }

	    destroy_desc(s);
	    /*longjmp(jmpbuf,1);*/
	  } else {
	    desc[s].st&=~NOTYET;
	    desc[s].st|=CONN;
	    /*fprintf(stderr,"Socket %d: connexion etablie.\n",s);*/
	    /*longjmp(jmpbuf,1);*/
	  }
	}
	else { /* it's simply a write OK */
	  int ldata, lw;
	  tbuf *w;
	  /****** A REMPLIR *******************************************/


	  /*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, s, desc[s].w->wb->r);*/

	  /* slide some eventual data from the RB to the WB if
	     possible. This way, we'll really write the maximum we can
	     do !
	     */
	  /* fprintf(stderr,"ligne %d: s=%d w=0x%08X w->r=%08X, w->w=%08X, w->l=%08X\n",
	     __LINE__,s, desc[s].w->wb,
	     desc[s].w->wb->r, desc[s].w->wb->w, desc[s].w->wb->l);*/
	  transfer_data(desc[s].w);


	  /*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, s, desc[s].w->wb->r);*/

	  w=desc[s].w->wb;
	  if (w->l > 0) {
	    if (w->w > w->r)
	      ldata = w->w - w->r;
	    else
	      ldata = bufsize - w->r;

	    /*fprintf(stderr,"Ligne %d: w->data=0x%08X, w->r=%d, ldata=%d\n",__LINE__, w->data, w->r, ldata);*/

	    /*lw=send(s, w->data+w->r, ldata, 0);*/
	    lw=write(s, w->data+w->r, ldata);

	    /*fprintf(stderr,"write(%d)=%d, ldata=%d\n", s, lw, ldata);*/

	    /*fprintf(stderr,"Ligne %d\n",__LINE__);*/

	    /*fprintf(stderr,"%d/%d bytes ecrits sur %d\n",lw,ldata,s);*/
	    if (lw>0) {
	      w->r = ( w->r + lw ) % bufsize;
	      w->l -= lw;
	    }
	    else
	      shutdesc(s,WSHUT); /* no more writes there */
	  }
	  /****** A REMPLIR *******************************************/
	  /* quite a good optimization: if the buffer is empty
	     (which is often the case after a write), then its
	     pointers are taken back to the beginning so that it
	     is faster to access at once.
	     */
	  if (w->l==0) {
	    w->r=w->w=0;
	  }
	}
      }
      s++;
    }

    /* Now examine the read descriptors */
    for (s=0;s<highdesc;) {
      /* this test allows to skip the empty descriptors block by block */
#if 0
      if (!((long int*)rfd)[s/FD_SETSIZE]) {
	s=(s%FD_SETSIZE)+FD_SETSIZE;
	continue;
      }
#endif
      if (FD_ISSET(s,rfd)) {
	int freespace, lr, i;
	tbuf *r;

	/****** A REMPLIR *******************************************/

	/*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, 5,  (desc[5].w)?desc[5].w->wb->r:-1);*/


	/*fprintf(stderr,"ligne %d: r->l=%d\n",__LINE__,desc[s].r->wb->l);*/
	transfer_data(desc[s].r);

	/*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, 5,  (desc[5].w)?desc[5].w->wb->r:-1);*/

	r=desc[s].r->rb;

	if (r->r > r->w)
	  freespace = bufsize - r->l;
	else if (r->l<bufsize)
	  freespace = bufsize - r->w;
	else
	  continue; /* no need to read 0 bytes to a full buffer !*/


	/*fprintf(stderr,"Ligne %d, s=%d, r->w=%d w->r=%d\n",__LINE__, s,  r->w, (desc[5].w)?desc[5].w->wb->r:-1);*/

	/*lr=recv(s, r->data + r->w, freespace, 0);*/
	/*		lr=0;
			do {
			i=lr;*/
	lr=read(s, &r->data[r->w], freespace);
	/*			fprintf(stderr,"lr=%d\n",lr);
				} while (lr>0);
				lr=i;*/

	/*fprintf(stderr,"Read(%d)=%d, freespace was %d\n", s, lr, freespace);*/
	/*		fprintf(stderr,"ligne %d: s=%d w=0x%08X w->r=%d, w->w=%d, w->l=%d\n",
			__LINE__,s, desc[s].w->wb,
			desc[s].w->wb->r, desc[s].w->wb->w, desc[s].w->wb->l);*/

	/*fprintf(stderr,"%d/%d bytes lus sur %d\n",lr,freespace,s);*/
	if (lr>0) {

	  /*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, 5,  (desc[5].w)?desc[5].w->wb->r:-1);*/

	  r->w = ( r->w + lr ) % bufsize;
	  r->l += lr;

	  /*fprintf(stderr,"Ligne %d, s=%d, w->r=%d\n",__LINE__, 5,  (desc[5].w)?desc[5].w->wb->r:-1);*/

	}
	else
	  shutdesc(s,RSHUT); /* no more data to read from here */
	/****** A REMPLIR *******************************************/
      }
      s++;
    }

  }
}

void usage() {
  fprintf(stderr,"ZPROX v 0.3 - (C) 980608 Willy Tarreau <tarreau@aemiaif.lip6.fr>\n" \
	  "ZPROX is a proxy with on-the-fly compression/decompression using ZLIB.\n" \
	  "Usage:\n" \
	  "       zprox [-iX] [-oX] [-p port] [-h] [-P port] [-b bufsize] -x host\n" \
	  "             [-d nbdesc] [-m memory]\n" \
	  "          -iX defines the algorithm to be used for incoming data (see X below).\n"\
	  "          -oX defines the algorithm to be used for outgoing data.\n" \
	  "            X equals '0' for simple copy (default), 'u' to\n" \
	  "            decompress on the fly, and 1-9 to allow same\n" \
	  "            compression as GZIP.\n" \
	  "          -p port defines the listening port (default: %d).\n" \
	  "          -P port specifies the port on the remote host (default: %d).\n" \
	  "          -b size defines the new buffer size (default: %d).\n" \
	  "          -x host specifies the host on which to connect.\n" \
	  "      Special options:\n"\
	  "          -d nbdesc : sets the maximum number of sockets (default: %d)\n"\
	  "          -m memory : #of permanent channels in memory (default: %d)\n"\
	  "\n"\
	  "ZPROX's main usage may be to speed up modem lines between 2 networks.\n"\
	  "Examples: speeding up Squid between the client and the remote proxy (one way):\n"\
	  "           zprox -o6 -i0 -b 4096 -p 3125 -x wwwproxy (on the remote server)\n"\
	  "           zprox -iu -o0 -b 4096 -P 3125 -p 3128 -x wwwproxy  (client side).\n"\
	  "\n",
	  LOCALPORT, REMOTEPORT, BUFSIZE, NBDESC, CHNBFREE);

  exit(1);
}

main(int argc, char **argv) {
  int i, c;

  for (i=1; i<argc; i++) {
    if (!strncmp(argv[i],"-i",2)) {
      c=argv[i][2];
      if (c>'0' && c<='9') {
	inaction=ACT_DEFLATE;
	gzilevel=c-'0';
      }
      else if (c=='0')
	inaction=ACT_COPY;
      else if (c=='u')
	inaction=ACT_INFLATE;
      else
	usage();
    }
    else if (!strncmp(argv[i],"-o",2)) {
      c=argv[i][2];
      if (c>'0' && c<='9') {
	outaction=ACT_DEFLATE;
	gzolevel=c-'0';
      }
      else if (c=='0')
	outaction=ACT_COPY;
      else if (c=='u')
	outaction=ACT_INFLATE;
    } else if (!strcmp(argv[i],"-h")) {
      usage();
    } else if (!strcmp(argv[i],"-p")) {
      if (argc>++i) {
	localport=atol(argv[i]);
      }
      else usage();
    } else if (!strcmp(argv[i],"-P")) {
      if (argc>++i) {
	remoteport=atol(argv[i]);
      }
      else usage();
    } else if (!strcmp(argv[i],"-x")) {
      if (argc>++i) {
	remotehost=argv[i];
      }
      else usage();
    } else if (!strcmp(argv[i],"-b")) {
      if (argc>++i) {
	bufsize=atoi(argv[i]);
      }
      else usage();
    } else if (!strcmp(argv[i],"-d")) {
      if (argc>++i) {
	nbdesc=atoi(argv[i]);
      }
	  else usage();
    } else if (!strcmp(argv[i],"-m")) {
      if (argc>++i) {
	chnormfree=atoi(argv[i]);
      }
      else usage();
    }
	else usage();
  }
  if (!remotehost)
    usage();
  init();
  select_loop();
}
