/************************************************************************
 *   IRC - Internet Relay Chat, iauth/a_io.c
 *   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 const volatile char rcsid[] = "@(#)$Id: a_io.c,v 1.31 2005/01/03 17:33:55 q Exp $";
#endif

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

anAuthData 	cldata[MAXCONNECTIONS]; /* index == ircd fd */
static int	cl_highest = -1;
#if defined(USE_POLL)
static int	fd2cl[MAXCONNECTIONS]; /* fd -> cl mapping */
#endif

#define IOBUFSIZE 4096
static char		iobuf[IOBUFSIZE+1];
static char		rbuf[IOBUFSIZE+1];	/* incoming ircd stream */
static int		iob_len = 0, rb_len = 0;

void	init_io(void)
{
    bzero((char *) cldata, sizeof(cldata));
}

/* sendto_ircd() functions */
void	vsendto_ircd(char *pattern, va_list va)
{
	char	ibuf[4096];

	vsprintf(ibuf, pattern, va);
	DebugLog((ALOG_DSPY, 0, "To ircd: [%s]", ibuf));
	strcat(ibuf, "\n");
	if (write(0, ibuf, strlen(ibuf)) != strlen(ibuf))
	    {
		sendto_log(ALOG_DMISC, LOG_NOTICE, "Daemon exiting. [w %s]",
			   strerror(errno));
		exit(0);
	    }
}

void	sendto_ircd(char *pattern, ...)
{
        va_list va;
        va_start(va, pattern);
        vsendto_ircd(pattern, va);
        va_end(va);
}

/*
 * next_io
 *
 *	given an entry, look for the next module instance to start
 */
static	void	next_io(int cl, AnInstance *last)
{
    DebugLog((ALOG_DIO, 0, "next_io(#%d, %x): last=%s state=0x%X", cl, last,
	      (last) ? last->mod->name : "", cldata[cl].state));

    /* first, bail out immediately if the entry is flagged A_DONE */
    if (cldata[cl].state & A_DONE)
	    return;

    /* second, make sure the last instance which ran cleaned up */
    if (cldata[cl].rfd > 0 || cldata[cl].wfd > 0)
	{
	    /* last is defined here */
	    sendto_log(ALOG_IRCD|ALOG_DMISC, LOG_ERR,
		       "module \"%s\" didn't clean up fd's! (%d %d)",
		       last->mod->name, cldata[cl].rfd, cldata[cl].wfd);
	    if (cldata[cl].rfd > 0)
		    close(cldata[cl].rfd);
	    if (cldata[cl].wfd > 0 && cldata[cl].rfd != cldata[cl].wfd)
		    close(cldata[cl].wfd);
	    cldata[cl].rfd = cldata[cl].wfd = 0;
	}
		       
    cldata[cl].buflen = 0;
    cldata[cl].mod_status = 0;
    cldata[cl].instance = NULL;
    cldata[cl].timeout = 0;

    /* third, if A_START is set, a new pass has to be started */
    if (cldata[cl].state & A_START)
	{
	    cldata[cl].state ^= A_START;
	    DebugLog((ALOG_DIO, 0, "next_io(#%d, %x): Starting again",
		      cl, last));
	    last = NULL; /* start from beginning */
	}

    /* fourth, find next instance to be ran */
    if (last == NULL)
	{
	    cldata[cl].instance = instances;
	    cldata[cl].ileft = 0;
	}
    else
	    cldata[cl].instance = last->nexti;

    while (cldata[cl].instance)
	{
	    int cm;

	    if (CheckBit(cldata[cl].idone, cldata[cl].instance->in))
		{
		    DebugLog((ALOG_DIO, 0,
	      "conf_match(#%d, %x, goth=%d, noh=%d) skipped %x (%s)",
			      cl, last, (cldata[cl].state & A_GOTH) == A_GOTH,
			      (cldata[cl].state & A_NOH) == A_NOH,
			      cldata[cl].instance,
			      cldata[cl].instance->mod->name));
		    cldata[cl].instance = cldata[cl].instance->nexti;
		    continue;
		}
	    cm = conf_match(cl, cldata[cl].instance);
	    DebugLog((ALOG_DIO, 0,
	      "conf_match(#%d, %x, goth=%d, noh=%d) said \"%s\" for %x (%s)",
		      cl, last, (cldata[cl].state & A_GOTH) == A_GOTH,
		      (cldata[cl].state & A_NOH) == A_NOH,
		      (cm==-1) ? "no match" : (cm==0) ? "match" : "try again",
		      cldata[cl].instance, cldata[cl].instance->mod->name));
	    if (cm == 0)
		    break;
	    if (cm == -1)
		    SetBit(cldata[cl].idone, cldata[cl].instance->in);
	    else /* cm == 1 */
		    cldata[cl].ileft += 1;
	    cldata[cl].instance = cldata[cl].instance->nexti;
	}

    if (cldata[cl].instance == NULL)
	    /* fifth, when there's no instance to try.. */
	{
	    DebugLog((ALOG_DIO, 0,
		      "next_io(#%d, %x): no more instances to try (%d)",
		      cl, last, cldata[cl].ileft));
	    if (cldata[cl].ileft == 0)
		{
		    /* we are done */
		    sendto_ircd("D %d %s %u ", cl, cldata[cl].itsip,
				cldata[cl].itsport);
                    cldata[cl].state |= A_DONE;
		    free(cldata[cl].inbuffer);
		    cldata[cl].inbuffer = NULL;
		}
	    return;
	}
    else
	    /* sixth, we've got an instance to try */
	{
	    int r;

	    if (cldata[cl].instance->delayed &&
			!(cldata[cl].state & A_DELAYEDSENT))
		{
		    /* fake to ircd that we're done */
		    sendto_ircd("D %d %s %u ", cl, cldata[cl].itsip,
				cldata[cl].itsport);
		    cldata[cl].state |= A_DELAYEDSENT;
		}

	    cldata[cl].timeout = time(NULL) + cldata[cl].instance->timeout;
	    r = cldata[cl].instance->mod->start(cl);
	    DebugLog((ALOG_DIO, 0,
		      "next_io(#%d, %x): %s->start() returned %d",
		      cl, last, cldata[cl].instance->mod->name, r));
	    if (r != 1)
		    /* started, or nothing to do or failed: don't try again */
		    SetBit(cldata[cl].idone, cldata[cl].instance->in);
	    if (r == 1)
		    cldata[cl].ileft += 1;
	    if (r != 0)
		    /* start() didn't start something */
		    next_io(cl, cldata[cl].instance);
	}
}

/*
 * parse_ircd
 *
 *	parses data coming from ircd (doh ;-)
 */
static	void	parse_ircd(void)
{
	char *ch, *chp, *buf = iobuf;
	int cl = -1, ncl;

	iobuf[iob_len] = '\0';
	while ((ch = index(buf, '\n')))
	    {
		*ch = '\0';
		DebugLog((ALOG_DSPY, 0, "parse_ircd(): got [%s]", buf));

		cl = atoi(chp = buf);
		if (cl >= MAXCONNECTIONS)
		    {
			sendto_log(ALOG_IRCD, LOG_CRIT,
			   "Recompile iauth, (fatal %d>=%d)", cl, MAXCONNECTIONS);
			exit(1);
		    }
		while (*chp++ != ' ');
		switch (chp[0])
		    {
		case 'C': /* new connection */
		case 'O': /* old connection: do nothing, just update data */
			if (cldata[cl].state & A_ACTIVE)
			    {
				/* this is not supposed to happen!!! */
                                sendto_log(ALOG_IRCD, LOG_CRIT,
			   "Entry %d [%c] is already active (fatal)!",
					   cl, chp[0]);
				exit(1);
			    }
			if (cldata[cl].instance || cldata[cl].rfd > 0 ||
			    cldata[cl].wfd > 0)
			    {
				sendto_log(ALOG_IRCD, LOG_CRIT,
				   "Entry %d [%c] is already active! (fatal)",
					   cl, chp[0]);
				exit(1);
			    }
			if (cldata[cl].authuser)
			    {
				/* shouldn't be here - hmmpf */
				sendto_log(ALOG_IRCD|ALOG_DIO, LOG_WARNING,
					   "Unreleased data [%c %d]!", chp[0],
					   cl);
				free(cldata[cl].authuser);
				cldata[cl].authuser = NULL;
			    }
			if (cldata[cl].inbuffer)
			    {
				/* shouldn't be here - hmmpf */
				sendto_log(ALOG_IRCD|ALOG_DIO, LOG_WARNING,
					   "Unreleased buffer [%c %d]!",
					   chp[0], cl);
				free(cldata[cl].inbuffer);
				cldata[cl].inbuffer = NULL;
			    }
			cldata[cl].user[0] = '\0';
			cldata[cl].passwd[0] = '\0';
			cldata[cl].host[0] = '\0';
			bzero(cldata[cl].idone, BDSIZE);
			cldata[cl].buflen = 0;
			if (chp[0] == 'C')
				cldata[cl].state = A_ACTIVE;
			else
			    {
				cldata[cl].state = A_ACTIVE|A_IGNORE;
				break;
			    }
			if (sscanf(chp+2, "%[^ ] %hu %[^ ] %hu",
				   cldata[cl].itsip, &cldata[cl].itsport,
				   cldata[cl].ourip, &cldata[cl].ourport) != 4)
			    {
				sendto_log(ALOG_IRCD, LOG_CRIT,
					   "Bad data from ircd [%s] (fatal)",
					   chp);
				exit(1);
			    }
			/* we should really be using a pool of buffer here */
			cldata[cl].inbuffer = malloc(INBUFSIZE+1);
			if (cl > cl_highest)
				cl_highest = cl;
			next_io(cl, NULL); /* get started */
			break;
		case 'D': /* client disconnect */
			if (!(cldata[cl].state & A_ACTIVE))
				/*
				** this is not fatal, it happens with servers
				** we connected to (and more?).
				** It's better/safer to ignore here rather
				** than try to filter in ircd. -kalt
				*/
                                sendto_log(ALOG_IRCD, LOG_WARNING,
				   "Warning: Entry %d [D] is not active.", cl);
			cldata[cl].state = 0;
			if (cldata[cl].rfd > 0 || cldata[cl].wfd > 0)
				cldata[cl].instance->mod->clean(cl);
			cldata[cl].instance = NULL;
			/* log something here? hmmpf */
			if (cldata[cl].authuser)
				free(cldata[cl].authuser);
			cldata[cl].authuser = NULL;
			if (cldata[cl].inbuffer)
				free(cldata[cl].inbuffer);
			cldata[cl].inbuffer = NULL;
			break;
		case 'R': /* fd remap */
			if (!(cldata[cl].state & A_ACTIVE))
			    {
				/* this should really not happen */
				sendto_log(ALOG_IRCD, LOG_CRIT,
					   "Entry %d [R] is not active!", cl);
				break;
			    }
			ncl = atoi(chp+2);
			if (cldata[ncl].state & A_ACTIVE)
			    {
				/* this is not supposed to happen!!! */
				sendto_log(ALOG_IRCD, LOG_CRIT,
				   "Entry %d [R] is already active (fatal)!", ncl);
				exit(1);
			    }
			if (cldata[ncl].instance || cldata[ncl].rfd > 0 ||
			    cldata[ncl].wfd > 0)
			    {
				sendto_log(ALOG_IRCD, LOG_CRIT,
				   "Entry %d is already active! (fatal)", ncl);
				exit(1);
			    }
			if (cldata[ncl].authuser)
			    {
				/* shouldn't be here - hmmpf */
				sendto_log(ALOG_IRCD|ALOG_DIO, LOG_WARNING,
				   "Unreleased data [%d]!", ncl);
				free(cldata[ncl].authuser);
				cldata[ncl].authuser = NULL;
			    }
			if (cldata[ncl].inbuffer)
			    {
				/* shouldn't be here - hmmpf */
				sendto_log(ALOG_IRCD|ALOG_DIO, LOG_WARNING,
					   "Unreleased buffer [%c %d]!",
					   chp[0], ncl);
				free(cldata[ncl].inbuffer);
				cldata[ncl].inbuffer = NULL;
			    }
			bcopy(cldata+cl, cldata+ncl, sizeof(anAuthData));

			cldata[cl].state = 0;
			cldata[cl].rfd = cldata[cl].wfd = 0;
			cldata[cl].instance = NULL;
			cldata[cl].authuser = NULL;
			cldata[cl].inbuffer = NULL;
			/*
			** this is the ugly part of having a slave (considering
			** that ircd remaps fd's: there is lag between the
			** server and the slave.
			** I can't think of any better way to handle this at
			** the moment -kalt
			*/
			if (cldata[ncl].state & A_IGNORE)
				break;
			if (cldata[ncl].state & A_LATE)
				/* pointless 99.9% of the time */
				break;
			if (cldata[ncl].authuser)
				sendto_ircd("%c %d %s %u %s", 
					    (cldata[ncl].state&A_UNIX)?'U':'u',
					    ncl, cldata[ncl].itsip,
                                            cldata[ncl].itsport,
					    cldata[ncl].authuser);
			if (cldata[ncl].state & A_DENY)
				sendto_ircd("K %d %s %u ", ncl,
					    cldata[ncl].itsip,
					    cldata[ncl].itsport,
                                            cldata[ncl].authuser);
			if (cldata[ncl].state & A_DONE)
				sendto_ircd("D %d %s %u ", ncl,
					    cldata[ncl].itsip,
					    cldata[ncl].itsport,
                                            cldata[ncl].authuser);
			break;
		case 'N': /* hostname */
			if (!(cldata[cl].state & A_ACTIVE))
			    {
				/* let's be conservative and just ignore */
                                sendto_log(ALOG_IRCD, LOG_WARNING,
				   "Warning: Entry %d [N] is not active.", cl);
				break;
			    }
			if (cldata[cl].state & A_IGNORE)
				break;
			strcpy(cldata[cl].host, chp+2);
			cldata[cl].state |= A_GOTH|A_START;
			if (cldata[cl].instance == NULL)
				next_io(cl, NULL);
			break;
		case 'A': /* host alias */
			if (!(cldata[cl].state & A_ACTIVE))
			    {
				/* let's be conservative and just ignore */
                                sendto_log(ALOG_IRCD, LOG_WARNING,
				   "Warning: Entry %d [A] is not active.", cl);
				break;
			    }
			if (cldata[cl].state & A_IGNORE)
				break;
			/* hmmpf */
			break;
		case 'U': /* user provided username */
			if (!(cldata[cl].state & A_ACTIVE))
			    {
				/* let's be conservative and just ignore */
                                sendto_log(ALOG_IRCD, LOG_WARNING,
				   "Warning: Entry %d [U] is not active.", cl);
				break;
			    }
			if (cldata[cl].state & A_IGNORE)
				break;
			strcpy(cldata[cl].user, chp+2);
			cldata[cl].state |= A_GOTU|A_START;
			if (cldata[cl].instance == NULL)
				next_io(cl, NULL);
			break;
		case 'P': /* user provided password */
			if (!(cldata[cl].state & A_ACTIVE))
			    {
				/* let's be conservative and just ignore */
                                sendto_log(ALOG_IRCD, LOG_WARNING,
				   "Warning: Entry %d [P] is not active.", cl);
				break;
			    }
			if (cldata[cl].state & A_IGNORE)
				break;
			strcpy(cldata[cl].passwd, chp+2);
			cldata[cl].state |= A_GOTP;
			/*
			** U message will follow immediately, 
			** no need to do any thing else here
			*/
			break;
		case 'T': /* ircd is registering the client */
			/* what to do with this? abort/continue? */
			cldata[cl].state |= A_LATE;
			break;
		case 'd': /* DNS timeout */
		case 'n': /* No hostname information, but no timeout either */
			if (!(cldata[cl].state & A_ACTIVE))
			    {
				/* let's be conservative and just ignore */
                                sendto_log(ALOG_IRCD, LOG_WARNING,
				   "Warning: Entry %d [%c] is not active.", 
					   cl, chp[0]);
				break;
			    }
			cldata[cl].state |= A_NOH|A_START;
			if (cldata[cl].instance == NULL)
				next_io(cl, NULL);
			break;
		case 'E': /* error message from ircd */
			sendto_log(ALOG_DIRCD, LOG_DEBUG,
				   "Error from ircd: %s", chp);
			break;
		case 'M':
			/* RPL_HELLO to be exact, but who cares. */
			strConnLen = sprintf(strConn, ":%s 020 * :", chp+2);
			break;
		default:
			sendto_log(ALOG_IRCD, LOG_ERR, "Unexpected data [%s]",
				   chp);
			break;
		    }

		buf = ch+1;
	    }
	rb_len = 0; iob_len = 0;
	if (strlen(buf))
		bcopy(buf, rbuf, rb_len = strlen(buf));
}

/*
 * loop_io
 *
 *	select()/poll() loop
 */
void	loop_io(void)
{
    /* the following is from ircd/s_bsd.c */
#if !defined(USE_POLL)
# define SET_READ_EVENT( thisfd )       FD_SET( thisfd, &read_set)
# define SET_WRITE_EVENT( thisfd )      FD_SET( thisfd, &write_set)
# define CLR_READ_EVENT( thisfd )       FD_CLR( thisfd, &read_set)
# define CLR_WRITE_EVENT( thisfd )      FD_CLR( thisfd, &write_set)
# define TST_READ_EVENT( thisfd )       FD_ISSET( thisfd, &read_set)
# define TST_WRITE_EVENT( thisfd )      FD_ISSET( thisfd, &write_set)

        fd_set  read_set, write_set;
        int     highfd = -1;
#else
/* most of the following use pfd */
# define POLLSETREADFLAGS       (POLLIN|POLLRDNORM)
# define POLLREADFLAGS          (POLLSETREADFLAGS|POLLHUP|POLLERR)
# define POLLSETWRITEFLAGS      (POLLOUT|POLLWRNORM)
# define POLLWRITEFLAGS         (POLLOUT|POLLWRNORM|POLLHUP|POLLERR)

# define SET_READ_EVENT( thisfd ){  CHECK_PFD( thisfd );\
                                   pfd->events |= POLLSETREADFLAGS;}
# define SET_WRITE_EVENT( thisfd ){ CHECK_PFD( thisfd );\
                                   pfd->events |= POLLSETWRITEFLAGS;}

# define CLR_READ_EVENT( thisfd )       pfd->revents &= ~POLLSETREADFLAGS
# define CLR_WRITE_EVENT( thisfd )      pfd->revents &= ~POLLSETWRITEFLAGS
# define TST_READ_EVENT( thisfd )       pfd->revents & POLLREADFLAGS
# define TST_WRITE_EVENT( thisfd )      pfd->revents & POLLWRITEFLAGS

# define CHECK_PFD( thisfd )                    \
        if ( pfd->fd != thisfd ) {              \
                pfd = &poll_fdarray[nbr_pfds++];\
                pfd->fd     = thisfd;           \
                pfd->events = 0;                \
					    }

        struct pollfd   poll_fdarray[MAXCONNECTIONS];
        struct pollfd * pfd     = poll_fdarray;
        int        nbr_pfds = 0;
#endif

	int i, nfds = 0;
	struct timeval wait;
	time_t now = time(NULL);

#if !defined(USE_POLL)
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);
	highfd = 0;
#else
	/* set up such that CHECK_FD works */
	nbr_pfds = 0;
	pfd      = poll_fdarray;
	pfd->fd  = -1;
#endif  /* USE_POLL */

	SET_READ_EVENT(0); nfds = 1;		/* ircd stream */
#if defined(USE_POLL) && defined(IAUTH_DEBUG)
	for (i = 0; i < MAXCONNECTIONS; i++)
		fd2cl[i] = -1; /* sanity */
#endif
	for (i = 0; i <= cl_highest; i++)
	    {
		if (cldata[i].timeout && cldata[i].timeout < now &&
		    cldata[i].instance /* shouldn't be needed.. but it is */)
		    {
			DebugLog((ALOG_DIO, 0,
				  "io_loop(): module %s timeout [%d]",
				  cldata[i].instance->mod->name, i));
			if (cldata[i].instance->mod->timeout(i) != 0)
				next_io(i, cldata[i].instance);
		    }
		if (cldata[i].rfd > 0)
		    {
			SET_READ_EVENT(cldata[i].rfd);
#if !defined(USE_POLL)
			if (cldata[i].rfd > highfd)
				highfd = cldata[i].rfd;
#else
			fd2cl[cldata[i].rfd] = i;
#endif
			nfds++;
		    }
		else if (cldata[i].wfd > 0)
		    {
			SET_WRITE_EVENT(cldata[i].wfd);
#if !defined(USE_POLL)
			if (cldata[i].wfd > highfd)
				highfd = cldata[i].wfd;
#else
			fd2cl[cldata[i].wfd] = i;
#endif
			nfds++;
		    }
	    }

	DebugLog((ALOG_DIO, 0, "io_loop(): checking for %d fd's", nfds));
	wait.tv_sec = 5; wait.tv_usec = 0;
#if !defined(USE_POLL)
	nfds = select(highfd + 1, (SELECT_FDSET_TYPE *)&read_set,
		      (SELECT_FDSET_TYPE *)&write_set, 0, &wait);
	DebugLog((ALOG_DIO, 0, "io_loop(): select() returned %d, errno = %d",
		  nfds, errno));
#else
	nfds = poll(poll_fdarray, nbr_pfds,
		    wait.tv_sec * 1000 + wait.tv_usec/1000 );
	DebugLog((ALOG_DIO, 0, "io_loop(): poll() returned %d, errno = %d",
		  nfds, errno));
	pfd = poll_fdarray;
#endif
	if (nfds == -1)
	{
		if (errno == EINTR)
		{
			return;
		}
		else
		{
			sendto_log(ALOG_IRCD, LOG_CRIT,
				   "fatal select/poll error: %s",
				   strerror(errno));
			exit(1);
		}
	}
	if (nfds == 0)	/* end of timeout */
		return;

	/* no matter select() or poll() this is also fd # 0 */
	if (TST_READ_EVENT(0))
		nfds--;

#if !defined(USE_POLL)
	for (i = 0; i <= cl_highest && nfds; i++)
#else
	for (pfd = poll_fdarray+1; pfd != poll_fdarray+nbr_pfds && nfds; pfd++)
#endif
	    {
#if defined(USE_POLL)
		i = fd2cl[pfd->fd];
# if defined(IAUTH_DEBUG)
		if (i == -1)
		    {
			sendto_log(ALOG_DALL, LOG_CRIT,"io_loop(): fatal bug");
			exit(1);
		    }
# endif
#endif
		if (cldata[i].rfd <= 0 && cldata[i].wfd <= 0)
		    {
#if defined(USE_POLL)
			sendto_log(ALOG_IRCD, LOG_CRIT,
			   "io_loop(): fatal data inconsistency #%d (%d, %d)",
				   i, cldata[i].rfd, cldata[i].wfd);
			exit(1);
#else
			continue;
#endif
		    }
		if (cldata[i].rfd > 0 && TST_READ_EVENT(cldata[i].rfd))
		    {
			int len;
 
			len = recv(cldata[i].rfd,
				   cldata[i].inbuffer + cldata[i].buflen,
				   INBUFSIZE - cldata[i].buflen, 0);
			DebugLog((ALOG_DIO, 0, "io_loop(): i = #%d: recv(%d) returned %d, errno = %d", i, cldata[i].rfd, len, errno));
			if (len < 0)
			    {
				cldata[i].instance->mod->clean(i);
				next_io(i, cldata[i].instance);
			    }
			else
			    {
				cldata[i].buflen += len;
				if (cldata[i].instance->mod->work(i) != 0)
					next_io(i, cldata[i].instance);
				else if (len == 0)
				    {
					cldata[i].instance->mod->clean(i);
					next_io(i, cldata[i].instance);
				    }
			    }
			nfds--;
		    }
		else if (cldata[i].wfd > 0 && TST_WRITE_EVENT(cldata[i].wfd))
		    {
			if (cldata[i].instance->mod->work(i) != 0)
				next_io(i, cldata[i].instance);
			
			nfds--;
		    }
	    }

	/*
        ** no matter select() or poll() this is also fd # 0
	** this has to be done last (for the USE_POLL version) because
	** of R messages we may get from the server :/
	*/
#if defined(USE_POLL)
	pfd = poll_fdarray;
#endif
	if (TST_READ_EVENT(0))
	    {
		/* data from the ircd.. */
		while (1)
		    {
			if (rb_len)
				bcopy(rbuf, iobuf, iob_len = rb_len);
			if ((i=recv(0,iobuf+iob_len,IOBUFSIZE-iob_len,0)) <= 0)
			    {
				DebugLog((ALOG_DIO, 0, "io_loop(): recv(0) returned %d, errno = %d", i, errno));
				break;
			    }
			iob_len += i;
			DebugLog((ALOG_DIO, 0,
				  "io_loop(): got %d bytes from ircd [%d]", i,
				  iob_len));
			parse_ircd();
		    }
		if (i == 0)
		    {
			sendto_log(ALOG_DMISC, LOG_NOTICE,
				   "Daemon exiting. [r]");
			exit(0);
		    }
	    }

#if 0
/* stupid code that tries to find a bug, but does nothing --Q */
#if defined(IAUTH_DEBUG)
	if (nfds > 0)
		sendto_log(ALOG_DIO, 0, "io_loop(): nfds = %d !!!", nfds);
# if !defined(USE_POLL)
	/* the equivalent should be written for poll() */
	if (nfds == 0)
		while (i <= cl_highest)
		    {
			/* Q got core here, he had i=-1 */
			if (cldata[i].rfd > 0 && TST_READ_EVENT(cldata[i].rfd))
			    {
				/* this should not happen! */
				/* hmmpf */
			    }
			i++;
		    }
# endif
#endif
#endif
}

/*
 * set_non_blocking (ripped from ircd/s_bsd.c)
 */
static	void	set_non_blocking(int fd, char *ip, u_short port)
{
	int     res, nonb = 0;

#ifdef NBLOCK_POSIX
        nonb |= O_NONBLOCK;
#endif
#ifdef NBLOCK_BSD
        nonb |= O_NDELAY;
#endif
#ifdef NBLOCK_SYSV
        /* This portion of code might also apply to NeXT.  -LynX */
        res = 1;
 
        if (ioctl (fd, FIONBIO, &res) < 0)
                sendto_log(ALOG_IRCD, 0, "ioctl(fd,FIONBIO) failed for %s:%u",
			   ip, port);
#else   
        if ((res = fcntl(fd, F_GETFL, 0)) == -1)
                sendto_log(ALOG_IRCD, 0, "fcntl(fd, F_GETFL) failed for %s:%u",
			   ip, port);
        else if (fcntl(fd, F_SETFL, res | nonb) == -1)
                sendto_log(ALOG_IRCD, 0,
			   "fcntl(fd, F_SETL, nonb) failed for %s:%u",
			   ip, port);
#endif  
}

/*
 * tcp_connect
 *
 *	utility function for use in modules, creates a socket and connects
 *	it to an IP/port
 *
 *	Returns the fd
 */
int	tcp_connect(char *ourIP, char *theirIP, u_short port, char **error)
{
	int fd;
	static char errbuf[BUFSIZ];
	struct SOCKADDR_IN sk;

	fd = socket(AFINET, SOCK_STREAM, 0);
	if (fd < 0)
	    {
		sprintf(errbuf, "socket() failed: %s", strerror(errno));
		*error = errbuf;
		return -1;
	    }
	/*
	 * this bzero() shouldn't be needed.. should it?
	 * AIX 4.1.5 doesn't like not having it tho.. I have no clue why -kalt
	 */
	bzero((char *)&sk, sizeof(sk));
	sk.SIN_FAMILY = AFINET;
#if defined(INET6)
	if(!inetpton(AF_INET6, ourIP, sk.sin6_addr.s6_addr))
		bcopy(minus_one, sk.sin6_addr.s6_addr, IN6ADDRSZ);
#else
	sk.sin_addr.s_addr = inetaddr(ourIP);
#endif
	sk.SIN_PORT = htons(0);
	if (bind(fd, (SAP)&sk, sizeof(sk)) < 0)
	    {
		sprintf(errbuf, "bind() failed: %s", strerror(errno));
		*error = errbuf;
		close(fd);
		return -1;
	    }
	set_non_blocking(fd, theirIP, port);
#if defined(INET6)
	if(!inetpton(AF_INET6, theirIP, sk.sin6_addr.s6_addr))
		bcopy(minus_one, sk.sin6_addr.s6_addr, IN6ADDRSZ);
#else
	sk.sin_addr.s_addr = inetaddr(theirIP);
#endif
	sk.SIN_PORT = htons(port);
	if (connect(fd, (SAP)&sk, sizeof(sk)) < 0 && errno != EINPROGRESS)
	    {
		sprintf(errbuf, "connect() to %s %u failed: %s", theirIP, port,
			strerror(errno));
		*error = errbuf;
		close(fd);
		return -1;
	    }
	*error = NULL;
	return fd;
}

