/************************************************************************
 *   IRC - Internet Relay Chat, iauth/mod_webproxy.c
 *   Copyright (C) 2001 Dan Merillat
 *   
 *   Original code (iauth/mod_socks.c) is
 *   Copyright (C) 1998 Christophe Kalt
 *
 *   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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef lint
static char rcsid[] =
"@(#)$Id: mod_webproxy.c,v 1.5 2004/01/16 09:05:37 gvs Exp $";
#endif

#include "os.h"
#include "a_defines.h"
#define MOD_WEBGATE_C
#include "a_externs.h"

/****************************** PRIVATE *************************************/

#define CACHETIME 30

struct proxylog {
	struct proxylog *next;
	char ip[HOSTLEN + 1];
	u_char state;				/* 0 = no proxy, 1 = open proxy, 2 = closed proxy */
	time_t expire;
	u_int port;
};

#define OPT_LOG		0x001
#define OPT_DENY    	0x002
#if 0
#define OPT_PARANOID	0x004
#endif

#define PROXY_NONE		0
#define PROXY_OPEN		1
#define PROXY_CLOSE		2
#define PROXY_UNEXPECTED	3
#define PROXY_BADPROTO		4

struct webproxy_private {
	struct proxylog *cache;
	u_int lifetime;
	u_char options;
	u_int ports[128];
	u_int portcount;
	/* stats */
	u_int chitc, chito, chitn, cmiss, cnow, cmax;
	u_int open[128], closed, noproxy;
};
int webproxy_start(u_int);

/*
 * webproxy_open_proxy
 *
 *  Found an open proxy for cl: deal with it!
 */

static void 
webproxy_open_proxy(cl,port)
int cl;
u_int port;
{
	struct webproxy_private *mydata = cldata[cl].instance->data;

	/* open proxy */
	if (mydata->options & OPT_DENY)
	    {
		cldata[cl].state |= A_DENY;
		sendto_ircd("K %d %s %u :open webproxy at %d", cl,
				cldata[cl].itsip, cldata[cl].itsport, port);
	    }
	if (mydata->options & OPT_LOG)
		sendto_log(ALOG_FLOG, LOG_INFO,
				"webproxy: open proxy: %s[%s], port %u",
				cldata[cl].host, cldata[cl].itsip, port);
}

/*
 * webproxy_add_cache
 *
 *  Add an entry to the cache.
 */

static void 
webproxy_add_cache(cl, state)
int cl, state;
{
	struct webproxy_private *mydata = cldata[cl].instance->data;
	struct proxylog *next;


	if (state == PROXY_OPEN) {
		if (cldata[cl].mod_status < mydata->portcount)
			mydata->open[cldata[cl].mod_status] += 1;
			
	} else if (state == PROXY_NONE)
		mydata->noproxy += 1;
	else
		/* state == PROXY_CLOSE|PROXY_UNEXPECTED|PROXY_BADPROTO */ 
		mydata->closed += 1;

	if (mydata->lifetime == 0)
		return;

	mydata->cnow += 1;
	if (mydata->cnow > mydata->cmax)
		mydata->cmax = mydata->cnow;

	next = mydata->cache;
	mydata->cache = (struct proxylog *) malloc(sizeof(struct proxylog));
	mydata->cache->expire = time(NULL) + mydata->lifetime;
	strcpy(mydata->cache->ip, cldata[cl].itsip);
	mydata->cache->port = mydata->ports[cldata[cl].mod_status - 1];
	mydata->cache->state = state;
	mydata->cache->next = next;
	DebugLog(
			(ALOG_DWEBPROXYC, 0,
			 "webproxy_add_cache(%d): new cache %s, open=%d", cl,
			 mydata->cache->ip, state));
}

/*
 * webproxy_check_cache
 *
 *  Check cache for an entry.
 */
static int 
webproxy_check_cache(cl)
{
	struct webproxy_private *mydata = cldata[cl].instance->data;
	struct proxylog **last, *pl;
	time_t now = time(NULL);

	if (!mydata || mydata->lifetime == 0)
		return 0;

	DebugLog(
				(ALOG_DWEBPROXYC, 0,
				 "webproxy_check_cache(%d): Checking cache for %s", cl,
				 cldata[cl].itsip));

	last = &(mydata->cache);
	while ((pl = *last)) {
		DebugLog((ALOG_DWEBPROXYC, 0, "webproxy_check_cache(%d): cache %s",
				  cl, pl->ip));
		if (pl->expire < now) {
			DebugLog((ALOG_DWEBPROXYC, 0,
					  "webproxy_check_cache(%d): free %s (%d < %d)",
					  cl, pl->ip, pl->expire, now));
			*last = pl->next;
			free(pl);
			mydata->cnow -= 1;
			continue;
		}
		if (!strcasecmp(pl->ip, cldata[cl].itsip)) {
			DebugLog((ALOG_DWEBPROXYC, 0,
					  "webproxy_check_cache(%d): match (%u)", cl,
					  pl->state));
			pl->expire = now + mydata->lifetime;	/* dubious */
			if (pl->state == PROXY_OPEN) {
				webproxy_open_proxy(cl,pl->port);
				mydata->chito += 1;
			} else if (pl->state == PROXY_NONE)
				mydata->chitn += 1;
			else
				mydata->chitc += 1;
			return -1;
		}
		last = &(pl->next);
	}
	mydata->cmiss += 1;
	return 0;
}

static int 
webproxy_write(u_int cl)
{

	struct webproxy_private *mydata = cldata[cl].instance->data;
	static u_char query[64];	/* big enough to hold all queries */
	static int query_len;	/* lenght of socks4 query */
	static int wlen;



	query_len=snprintf(query, 64, "CONNECT %s:%d HTTP/1.0\n\n",
			 cldata[cl].ourip, cldata[cl].ourport);

	DebugLog((ALOG_DWEBPROXY, 0, "webproxy_write(%d): Checking %s:%u",
			  cl, cldata[cl].itsip, mydata->ports[cldata[cl].mod_status]));
	if ((wlen=write(cldata[cl].wfd, query, query_len)) != query_len) {
		/* most likely the connection failed */
		DebugLog(
					(ALOG_DWEBPROXY, 0,
				  "webproxy_write(%d): write() failed: %d %s", cl,
					 wlen, strerror(errno)));
		close(cldata[cl].wfd);
		cldata[cl].rfd = cldata[cl].wfd = 0;
		cldata[cl].buflen=0;
		if (++cldata[cl].mod_status < mydata->portcount)
			return webproxy_start(cl);
		else
			return 1;
	}
	cldata[cl].rfd = cldata[cl].wfd;
	cldata[cl].wfd = 0;
	return 0;
}

static int 
webproxy_read(u_int cl)
{
	/* Looking for "Connection established"
	 * HTTP/1.0 200 Connection established" */

	struct webproxy_private *mydata = cldata[cl].instance->data;
	int again = 1;
	u_char state = PROXY_CLOSE;
	char  * lookfor="HTTP/1.0 200";
	u_int looklen=strlen(lookfor);

	/* data's in from the other end */
	if (cldata[cl].buflen < looklen)
		return 0;
	
	/* zero it out so the debug log is sane */
	cldata[cl].inbuffer[cldata[cl].buflen]=0;

	/* got all we need */
	DebugLog(
				(ALOG_DWEBPROXY, 0, "webproxy_read(%d): %d Got [%s]",
			cl, mydata->ports[cldata[cl].mod_status], cldata[cl].inbuffer));


	if (!strncmp(cldata[cl].inbuffer, lookfor, looklen) ||
	    !strncmp(cldata[cl].inbuffer, "HTTP/1.1 200",looklen)
			)  { /* got it! */
		DebugLog((ALOG_DWEBPROXY, 0, "webproxy_read(%d): Open proxy on %s:%u",
				cl, cldata[cl].itsip, mydata->ports[cldata[cl].mod_status]));
		state = PROXY_OPEN;
		webproxy_open_proxy(cl,mydata->ports[cldata[cl].mod_status]);
		again = 0;
	}

	cldata[cl].mod_status++;
	close(cldata[cl].rfd);
	cldata[cl].rfd = 0;

	if (again && cldata[cl].mod_status < mydata->portcount) {
		cldata[cl].buflen = 0;
		return webproxy_start(cl);
	}
	else {
		webproxy_add_cache(cl, state);
		return -1;
	}
	return 0;
}

/******************************** PUBLIC ************************************/

/*
 * webproxy_init
 *
 *  This procedure is called when a particular module is loaded.
 *  Returns NULL if everything went fine,
 *  an error message otherwise.
 */

char *
webproxy_init(AnInstance * self)
{
	struct webproxy_private *mydata;
	char tmpbuf[80], cbuf[32];
	static char txtbuf[80];
	u_int portcount = 0;
	
	tmpbuf[0] = txtbuf[0] = '\0';

	mydata = 
		(struct webproxy_private *)
		malloc(sizeof(struct webproxy_private));
	bzero((char *) mydata, sizeof(struct webproxy_private));
	mydata->cache = NULL;
	mydata->lifetime = CACHETIME;
	if (!self->opt)
	{
		/* using default config:
		** log,deny,ports=3128;8080 
		*/
		mydata->options=OPT_DENY|OPT_LOG;
		mydata->ports[portcount++] = 3128;
		mydata->ports[portcount++] = 8080;
		mydata->portcount=portcount;
		strcat(tmpbuf, ",log");
		strcat(txtbuf, ", Log");
		strcat(tmpbuf, ",reject");
		strcat(txtbuf, ", Reject");
		strcat(tmpbuf, ",ports=3128;8080");
		strcat(txtbuf, ", Ports=3128;8080");

	}
	else
	{

		char *ch = NULL, *portsp = NULL;
	
		if ((portsp=strstr(self->opt, "ports"))) 

		{
			char xbuf[128];
			char *c;
			size_t slen;
			bzero(xbuf,sizeof(xbuf));
			ch = strchr(portsp, '=');
			if (!ch)
			{
				mydata->ports[portcount++] = 3128;
				mydata->ports[portcount++] = 8080;
				mydata->portcount=portcount;
			}
			else
			{
				c=index(ch, ',');
				if (!c)
				   c=ch+strlen(ch);
			/* now ch is at = and c is at the end of the ports line */
				ch++;
				slen = (size_t) c - (size_t) ch;
				if (slen > 30)
				{
					slen = 30;
				}
				memcpy(xbuf, ch, slen);
				strcat(tmpbuf,",ports=");

				strcat(tmpbuf,xbuf);
				ch=xbuf;
				while((c=index(ch, ';'))) {
				   *c=0;
				   mydata->ports[portcount++]=atoi(ch);
				   ch=c+1;
				}
				mydata->ports[portcount++]=atoi(ch);
				mydata->portcount=portcount;
			}
		}
		if (strstr(self->opt, "log")) {
			mydata->options |= OPT_LOG;
			strcat(tmpbuf, ",log");
			strcat(txtbuf, ", Log");
		}
		if (strstr(self->opt, "reject")) {
			mydata->options |= OPT_DENY;
			strcat(tmpbuf, ",reject");
			strcat(txtbuf, ", Reject");
		}
		if (strstr(self->opt, "cache")) {
			char *ch = index(self->opt, '=');

			if (ch)
				mydata->lifetime = atoi(ch + 1);
		}
	}	
	sprintf(cbuf, ",cache=%d", mydata->lifetime);
	strcat(tmpbuf, cbuf);
	sprintf(cbuf, ", Cache %d (min)", mydata->lifetime);
	strcat(txtbuf, cbuf);
	mydata->lifetime *= 60;
	self->popt = mystrdup(tmpbuf + 1);
	self->data = mydata;
	return txtbuf + 2;
}

/*
 * webproxy_release
 *
 *  This procedure is called when a particular module is unloaded.
 */
void 
webproxy_release(self)
AnInstance *self;
{
	struct webproxy_private *mydata = self->data;
	free(mydata);
	free(self->popt);
}

/*
 * webproxy_stats
 *
 *  This procedure is called regularly to update statistics sent to ircd.
 */
void 
webproxy_stats(self)
AnInstance *self;
{
	struct webproxy_private *mydata = self->data;
	char mybuf[256];
	int len;
	int i;

	len=snprintf(mybuf, 256, "S webproxy port:open");
	for (i=0;i<mydata->portcount;i++)
		len+=snprintf(mybuf+len, 256-len, " %u:%u", 
				mydata->ports[i], mydata->open[i]);

	len+=snprintf(mybuf+len, 256-len, " closed %u noproxy %u",
			mydata->closed, mydata->noproxy);

	sendto_ircd(mybuf);

	sendto_ircd
		("S webproxy cache open %u closed %u noproxy %u miss %u (%u <= %u)",
		 mydata->chito, mydata->chitc, mydata->chitn, mydata->cmiss,
		 mydata->cnow, mydata->cmax);
}

/*
 * webproxy_start
 *
 *  This procedure is called to start the socks check procedure.
 *  Returns 0 if everything went fine,
 *  -1 otherwise (nothing to be done, or failure)
 *
 *  It is responsible for sending error messages where appropriate.
 *  In case of failure, it's responsible for cleaning up (e.g. webproxy_clean
 *  will NOT be called)
 */
int 
webproxy_start(cl)
u_int cl;
{

	struct webproxy_private *mydata = cldata[cl].instance->data;
	char *error;
	int fd;

	if (cldata[cl].state & A_DENY) {
		/* no point of doing anything */
		DebugLog((ALOG_DWEBPROXY, 0,
				  "webproxy_start(%d): A_DENY alredy set ", cl));
		return -1;
	}
	if (cldata[cl].mod_status == 0)
		if (webproxy_check_cache(cl))
			return -1;

	if (strchr(cldata[cl].itsip,':'))
		return -1;

	while (cldata[cl].mod_status < mydata->portcount) {
		DebugLog(
				  (ALOG_DWEBPROXY, 0, "webproxy_start(%d): Connecting to %s:%u",
				   cl, cldata[cl].itsip, mydata->ports[cldata[cl].mod_status]));
		fd = tcp_connect(cldata[cl].ourip, cldata[cl].itsip, 
					mydata->ports[cldata[cl].mod_status],
					&error);
		if (fd > 0) {
						/*so that webproxy_work() is called when connected */
			cldata[cl].wfd = fd;		
			return 0;
		}
		DebugLog((ALOG_DWEBPROXY, 0,
			  "webproxy_start(%d): tcp_connect() reported %s", cl,
			  error));
		cldata[cl].mod_status++;
		continue;
		
	}

	return -1;
}

/*
 * webproxy_work
 *
 *  This procedure is called whenever there's new data in the buffer.
 *  Returns 0 if everything went fine, and there is more work to be done,
 *  Returns -1 if the module has finished its work (and cleaned up).
 *
 *  It is responsible for sending error messages where appropriate.
 */
int 
webproxy_work(cl)
u_int cl;
{
	struct webproxy_private *mydata = cldata[cl].instance->data;

	if (!mydata)
		return -1;

	DebugLog(
			  (ALOG_DWEBPROXY, 0, "webproxy_work(%d): %d %d %d buflen=%d",
			   cl, mydata->ports[cldata[cl].mod_status],
				cldata[cl].rfd, cldata[cl].wfd,
			   cldata[cl].buflen));

	if (cldata[cl].wfd > 0)
		/*
		   ** We haven't sent the query yet, the connection was just
		   ** established.
		 */
		return webproxy_write(cl);
	else
		return webproxy_read(cl);
}

/*
 * webproxy_clean
 *
 *  This procedure is called whenever the module should interrupt its work.
 *  It is responsible for cleaning up any allocated data, and in particular
 *  closing file descriptors.
 */
void 
webproxy_clean(cl)
u_int cl;
{
	DebugLog((ALOG_DWEBPROXY, 0, "webproxy_clean(%d): cleaning up", cl));
	/*
	   ** only one of rfd and wfd may be set at the same time,
	   ** in any case, they would be the same fd, so only close() once
	 */
	if (cldata[cl].rfd)
		close(cldata[cl].rfd);
	else if (cldata[cl].wfd)
		close(cldata[cl].wfd);
	cldata[cl].rfd = cldata[cl].wfd = 0;
}

/*
 * webproxy_timeout
 *
 *  This procedure is called whenever the timeout set by the module is
 *  reached.
 *
 *  Returns 0 if things are okay, -1 if check was aborted.
 */
int 
webproxy_timeout(cl)
u_int cl;
{
	DebugLog(
				(ALOG_DWEBPROXY, 0,
				 "webproxy_timeout(%d): calling webproxy_clean ", cl));
	webproxy_clean(cl);
	return -1;
}

aModule Module_webproxy =
{"webproxy", webproxy_init, webproxy_release, webproxy_stats,
 webproxy_start, webproxy_work, webproxy_timeout, webproxy_clean
};


syntax highlighted by Code2HTML, v. 0.9.1