/* ZPROX 0.1.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) 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.1 1998/06/01 19:58:27 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>
#include <stdlib.h>
#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 "zlib.h"
#include <setjmp.h>

/* user configurable compilation-time parameters */
#define NBDESC		1024
#define BUFSIZE	        4096
#define LOCALPORT	3125
#define REMOTEPORT	3128
#define INACTION	ACT_COPY
#define OUTACTION	ACT_COPY
#define GZOLEVEL	6
#define GZILEVEL	6
#define RHIWAT		(BUFSIZE*7/8)
#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

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 GZIP/GUNZIP */
    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 *prev, *next; /* next and previous one in memory */
};

/* 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 */
};


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

/* memory pool for inflate/deflate */
char *gztmp;

/* 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;

/* 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 (%d) READ\n",d);*/
	shutdown(d,0);
    }
    if (mode&WSHUT) {
	/*fprintf(stderr,"Shutdown (%d) WRITE\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);
    if (d<nbdesc)
	desc[d].st=NOTUSED;
	

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

}

/* 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);
    /* free buffers */
    if (ch->rb) free(ch->rb);
    if (ch->wb) free(ch->wb);
    /* free ZLIB streams */
    if (ch->mode==ACT_DEFLATE)
	deflateEnd(&ch->strm);
    else if (ch->mode==ACT_INFLATE)
	inflateEnd(&ch->strm);

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

    free(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 ((gztmp=(char *)malloc(bufsize))==NULL) fatal("malloc() for gztmp");

    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:
   - GZIP   (when ch->mode==ACT_DEFLATE)
   - GUNZIP (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;

    /* As we work with contiguous blocks of data, we'll sometimes have to
       truncate 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.
       GZIP/GUNZIP 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)) || (ch->wb->l>=bufsize))
	/* if there's nothing to do or nowhere to store data, we return.
	   This asumption 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 */
    /******************************************************************/

    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;
	}
	/* replaces the 2 pointers at the beginning if the buffer is empty */
	if (!r->l)
	    r->r=r->w=0;
	ch->end = 1;
	return (nbtot>0);
    }



    /************************ compression mode ************************/
    /* in this mode of operation, this proxy acts like a standard one */
    /******************************************************************/


    /* 1 - source block length */

    if (r->l > 0) {
	if (r->w > r->r)
	    ldata = r->w - r->r;
	else
	    ldata = bufsize - r->r;
    }
    else
	ldata=0;

    /* 2 - dest space */
    if (w->r > w->w)
	freespace = bufsize - w->l;
    else /* if r==w, we know the buffer is empty and not full */
	freespace = bufsize - w->w;


    /* note that EVEN if there is no source data available, we MUST proceed
       with GZIP or GUNZIP since they will go on from their internal buffers. */
    ch->strm.next_in  = &r->data[r->r];
    ch->strm.avail_in = ldata;

    ch->strm.next_out = gztmp;
    ch->strm.avail_out= bufsize - w->l;

    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);*/

	r->r = ( r->r + ldata - ch->strm.avail_in ) % bufsize;
	r->l -= ldata - ch->strm.avail_in;

	outb = bufsize - w->l - ch->strm.avail_out;
	bcopy(gztmp, &w->data[w->w], (outb<freespace)?outb:freespace);
	if (outb>freespace) { /* this means the output buffer is cut in two parts */
	    bcopy(&gztmp[freespace],w->data,outb-freespace);
	}
	w->w = ((w->w) + outb) % bufsize;
	w->l += outb;
	/*w->w = ( w->w + freespace - ch->strm.avail_out ) % bufsize;
	w->l += freespace - ch->strm.avail_out;*/

	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);*/

	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;
	    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)");*/
	    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(sockout,F_SETFL,O_NONBLOCK)==-1) {
		perror("Warning: fcntl(SET O_NONBLOCK)");
	    }
	    if (connect(sockout,(struct sockaddr*)&remote,sizeof(remote))==-1) {
		if (errno!=EINPROGRESS) {
		    perror("connect()");
		    closedesc(s); closedesc(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=(tch*)malloc(sizeof(tch));
	    desc[s].w=desc[sockout].r=(tch*)malloc(sizeof(tch));
	    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;
	    desc[s].r->end=1; desc[s].w->end=1;
	    desc[sockout].r->end=1; desc[sockout].w->end=1;
	    
	    desc[s].r->strm.zalloc = desc[s].w->strm.zalloc = NULL;
	    desc[s].r->strm.zfree = desc[s].w->strm.zfree = NULL;
	    desc[s].r->strm.opaque = desc[s].w->strm.opaque = NULL;
	    /* 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);

	    /* 4 fresh buffers, as needed */
	    bzero((desc[s].r->rb=(tbuf*)malloc(sizeof(tbuf)+bufsize)),sizeof(tbuf)+bufsize);
	    bzero((desc[s].r->wb=(tbuf*)malloc(sizeof(tbuf)+bufsize)),sizeof(tbuf)+bufsize);
	    bzero((desc[s].w->rb=(tbuf*)malloc(sizeof(tbuf)+bufsize)),sizeof(tbuf)+bufsize);
	    bzero((desc[s].w->wb=(tbuf*)malloc(sizeof(tbuf)+bufsize)),sizeof(tbuf)+bufsize);
	    
	    /* 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 */

	    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);
		    getsockopt(s,SOL_SOCKET,SO_ERROR,&retval,&lretval);
		    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;
		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=read(s, &r->data[r->w], freespace);

		/*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.1 - (C) 980601 Willy Tarreau <tarreau@aemiaif.lip6.fr>\n" \
                   "ZPROX is a proxy with on-the-fly compress/uncompress using ZLIB.\n" \
                   "Usage:\n" \
                   "       zprox [-iX] [-oX] [-p port] [-h] [-P port] [-b bufsize] -x host\n" \
                   "             [-d nbdesc]\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" \
                   "            uncompress 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"\
	           "\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);

    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();
	}
    }
    if (!remotehost)
	usage();
    init();
    select_loop();
}
