/************************************************************************
 *   IRC - Internet Relay Chat, iauth/mod_lhex.c
 *   Copyright (C) 1998-1999 Christophe Kalt and Andrew Snare
 *
 *   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 const volatile char rcsid[] = "@(#)$Id mod_lhex.c,v 1.12 1999/02/06 21:43:52 kalt Exp $";
#endif

#include "os.h"
#include "a_defines.h"
#define MOD_LHEX_C
#include "a_externs.h"
#undef MOD_LHEX_C

/****************************** PRIVATE *************************************/
#define LHEXPORT	9674

struct lhex_private
{
	/* stats */
	u_int ok, banned;
	u_int tried, clean, timeout;
};

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

/*
 * lhex_init
 *
 *	This procedure is called when a particular module is loaded.
 *	Returns NULL if everything went fine,
 *	an error message otherwise.
 */
static	char	*lhex_init(AnInstance *self)
{
	struct lhex_private *mydata;

#if defined(INET6)
	return "IPv6 unsupported.";
#endif
	if(self->opt == NULL)
		return "Aie! no option(s): no LHEx server to connect to!";
	if(!inetaton(self->opt,NULL))
		return "Aie! Option wasn't a valid IP address!";

	/* Allocate the module data */
	mydata = (struct lhex_private *) malloc(sizeof(struct lhex_private));
	bzero((char *) mydata, sizeof(struct lhex_private));

	self->popt = mystrdup(self->opt);
	self->data = mydata;
	return NULL;
}

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

/*
 * lhex_stats
 *
 *	This procedure is called regularly to update statistics sent to ircd.
 */
static	void	lhex_stats(AnInstance *self)
{
	struct lhex_private *mydata = self->data;

	sendto_ircd("S lhex ok %u banned %u", mydata->ok, mydata->banned);
	sendto_ircd("S lhex tried %u aborted %u / %u",
		    mydata->tried, mydata->clean, mydata->timeout);
}

/*
 * lhex_start
 *
 *	This procedure is called to start the LHEx check procedure.
 *	Returns 0 if everything went fine,
 *	anything else 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. lhex_clean
 *	will NOT be called)
 */
static	int	lhex_start(u_int cl)
{
	char *error;
	int fd;
	struct lhex_private *mydata = cldata[cl].instance->data;

	if (cldata[cl].state & A_DENY)
	    {
		/* no point of doing anything */
		DebugLog((ALOG_DLHEX, 0,
			  "lhex_start(%d): A_DENY already set ", cl));
		return -1;
	    }

	DebugLog((ALOG_DLHEX, 0, "lhex_start(%d): Connecting to %s", cl,
		  cldata[cl].instance->opt));
	mydata->tried += 1;
	fd= tcp_connect(cldata[cl].ourip, cldata[cl].instance->opt,
	                LHEXPORT, &error);
	if (fd < 0)
	    {
		DebugLog((ALOG_DLHEX, 0,
			  "lhex_start(%d): tcp_connect() reported %s",
			  cl, error));
		return -1;
	    }

	cldata[cl].wfd = fd; /*so that lhex_work() is called when connected*/
	return 0;
}

/*
 * lhex_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.
 */
static	int	lhex_work(u_int cl)
{
    	DebugLog((ALOG_DLHEX, 0, "lhex_work(%d): %d %d buflen=%d", cl,
		  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.
		*/
		char query[3+7+6+4+USERLEN+2*HOSTLEN+8+3];/*strlen(atoi(cl))<=8*/
		char *ident = cldata[cl].authuser;

		/* This is part of every request */
		sprintf(query, "id:%u ip:%s", cl, cldata[cl].itsip);

		/* These bits are optional, depending on what's known */
		if (ident)
		    {
		    	strcat(query, " ident:");
			strcat(query, ident);
		    }
		if (cldata[cl].state & A_GOTH)
		    {
		    	strcat(query, " host:");
			strcat(query, cldata[cl].host);
		    }
		/* Terminate the request */
		strcat(query, "\r\n");

		DebugLog((ALOG_DLHEX, 0, "lhex_work(%u): Sending query [%s]",
			  cl, query));
		if (write(cldata[cl].wfd, query, strlen(query)) < 0)
		    {
			/* most likely the connection failed */
			DebugLog((ALOG_DLHEX, 0,
				  "lhex_work(%u): write() failed: %s",
				  cl, strerror(errno)));
			close(cldata[cl].wfd);
			cldata[cl].rfd = cldata[cl].wfd = 0;
			return 1;
		    }
		cldata[cl].rfd = cldata[cl].wfd;
		cldata[cl].wfd = 0;
	    }
	else
	    {
		/* data's in from the other end */
		char *ch, *nch;
		u_int id;
		int retval = 0;

		cldata[cl].inbuffer[cldata[cl].buflen] = '\0';
		nch = cldata[cl].inbuffer;
		while((nch < (cldata[cl].inbuffer + cldata[cl].buflen)) &&
		      (ch = index(nch, '\r')) && !retval)
		    {
			char *och = nch;
			nch = ch+2;		/* Skip the \r\n */
			*ch = '\0';
			DebugLog((ALOG_DLHEX, 0, "lhex_work(%u): Got [%s]",
				 cl, och));

			/* Have a go at parsing the return info */
			if(sscanf(och, "%u", &id) != 1)
				DebugLog((ALOG_DLHEX, 0, "lhex_work(%u): "
					 "Malformed data!", cl));
			else
			    {
				struct lhex_private *d=cldata[cl].instance->data;
				ch = index(och, ':');
				while(isspace(*(++ch)));
				if(!strcmp(ch,"OK"))
				    {
					d->ok++;
					DebugLog((ALOG_DLHEX, 0,
						 "lhex_work(%u): OK", id));
				    	close(cldata[cl].rfd);
					cldata[cl].rfd = 0;
					retval = -1;
				    }
				else if(!strcmp(ch,"Not OK"))
				    {
				        d->banned++;
					DebugLog((ALOG_DLHEX, 0,
					         "lhex_work(%u): Not OK", id));
					cldata[cl].state |= A_DENY;
					/* I really wish we could send the
					   client a "reason" here :P */
					sendto_ircd("K %d %s %u ", cl,
						    cldata[cl].itsip,
						    cldata[cl].itsport);
				    	close(cldata[cl].rfd);
					cldata[cl].rfd = 0;
					retval = -1;
				    }
				else
				    {
#if 0
				    	/* Call this info for the client */
					sendto_ircd("I %d %s %u NOTICE AUTH :%s",
						    cl, cldata[cl].itsip,
						    cldata[cl].itsport, ch);
#endif
				    	retval = 0;
				    }
			    }
		    }
	    	return retval;
	    }
	return 0;
}

/*
 * lhex_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.
 */
static	void	lhex_clean(u_int cl)
{
	struct lhex_private *mydata = cldata[cl].instance->data;

	mydata->clean += 1;
	DebugLog((ALOG_DLHEX, 0, "lhex_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;
}

/*
 * lhex_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.
 */
static	int	lhex_timeout(u_int cl)
{
	struct lhex_private *mydata = cldata[cl].instance->data;

	mydata->timeout += 1;
	DebugLog((ALOG_DLHEX, 0, "lhex_timeout(%d): calling lhex_clean ", cl));
	lhex_clean(cl);
	return -1;
}

aModule Module_lhex =
	{ "lhex", lhex_init, lhex_release, lhex_stats,
	  lhex_start, lhex_work, lhex_timeout, lhex_clean };

