#include "smtprelay.h"
#include "sys/time.h"
#include "signal.h"

/*
 * Switch for EndOfLine processing
 * 0	Accept \r \n \r\n \n\r
 * 1	Accept \n only, ignore all \r's
 * 2	Accept \r\n only, ignore lone \r's
 *	We see a lot of lines with imbedded \r in porn postings
 */
#define	NG_EOL	1
#if NG_EOL == 0
#   define	NG_ISTERM(x)	((x) == '\n' || (x) == '\r')
#else
#if NG_EOL == 1
#   define	NG_ISTERM(x)	((x) == '\n')
#else
#if NG_EOL == 2
#   define	NG_ISTERM(x)	((x) == '\n' && lastchar == '\r')
#endif
#endif
#endif

char	hostName[MAXHOSTNAMELEN];

static	void NewsAlarm(int);

/*
 * Initialize an incomming news connection
 */
NEWS *
NewsInit(
    int		ns,		/* news stream file descriptor */
    int		blocking	/* NI_BLOCK, NI_NONBLOCK */
)
{
    NEWS 	*nss;		/* news stream struct */
    int		flags;		/* socket mode flags */

    dprintf(DEBUG_FUN,(debugout,
	"NewsInit(ns=%d,blocking=%d(%s))\n",
	ns,
	blocking,
	blocking == NI_BLOCK ? "BLOCKING" :
	    blocking == NI_NONBLOCK ? "NON-BLOCKING" : "???"
    ));

    /*
     * Allocate and initialize news stream structure
     * Returns null value if calloc fails
     */
    if( (nss = (NEWS *)calloc(1,sizeof(NEWS))) == NULL)
    {
	LogError("Out memory allocating newstream");
    }
    else
    {
	nss->fd = ns;
	nss->nsbp = nss->nsb;
	nss->nsbs = 0;
    }

    /*
     * Setup Blocking/Non-Blocking
     */
    if ( (flags = fcntl(nss->fd,F_GETFL,0)) != -1)
    {
	/*
	 * Set or clear blocking mode
	 */
	if (blocking == NI_NONBLOCK)
	{
	    flags |= O_NONBLOCK;
	}
	else
	{
	    flags &= (~O_NONBLOCK);
	}
	if (fcntl(nss->fd,F_SETFL,flags) == -1)
	{
	    sprintf(
		logBuf,
		"Couldn't set connection mode to %s",
		blocking == NI_NONBLOCK ? "NON-BLOCKING" : "BLOCKING"
	    );
	    LogError(logBuf);
	}
    }
    else
    {
	LogError("Couldn't get connection mode");
    }
    return(nss);
}

/*
 * Close a news connection
 */
void
NewsClose(
    NEWS	*N		/* Stream to news server */
)
{
    dprintf(DEBUG_FUN,(debugout,
	"NewsClose(N==%p)\n",
	N
    ));

    if(N) {
	if(shutdown(N->fd,0)) LogError("Shutdown news socket");
	close(N->fd);
	free(N);
    }
}

/*
 * Send a news command
 *
 * If command is null, no command is sent but a response is expected.
 * This is how we handle the original connection message from the server.
 *
 * The returned message points to a static location
 * Returns -1 for an internal error otherwise, the command response number
 */
int
NewsCommand(
    NEWS	*N,		/* News server stream */
    char	*command, 	/* Command to send */
    char	**message	/* Returned message (static) */
)
{
    char	*wbp;		/* Write buffer pointer */
    int		wbs;		/* Write buffer size */
    int		ws;		/* Write size */
    static	char mbuf[512];	/* returned message buffer */
    char	tmbuf[sizeof(mbuf)+16];	/* tmp debug buffer */

    dprintf(DEBUG_FUN,(debugout,
	"NewsCommand(N=%p,command='%s',message=%p)\n",
	N,
	command,
	message
    ));

    if(message) *message = "";

    /*
     * Send command (if any) to news server
     */
    if(command) {
	if(strlen(command)) {
	    dprintf(DEBUG_DETAIL,(debugout,
		"NEWS -> %s\n",
		command
	    ));
	    if(NewsPuts(N,command,-1,NEWSPUTS_CRLF) < 0) {
		LogMsg("Write to news server");
		return(-1);
	    }
	}
    }

    /*
     * Get response from the server
     */
    if(NewsGets(N,mbuf,sizeof(mbuf),(int *)0,NEWSGETS_LINE)) {
	return(-1);
    }
    dprintf(DEBUG_DETAIL,(debugout,
	"NEWS <- %s",
	mbuf
    ));

    if(message) *message = mbuf;
    return(atoi(mbuf));
}

/*
 * Write a string to the news server
 * Appends CR+LF to string if NEWSPUTS_CRLF
 * Returns 0 if ok, -1 if error
 */
int
NewsPuts(
    NEWS	*N,		/* News stream */
    char	*mp,		/* Message string */
    int		msize,		/* Message size (-1 = use strlen()) */
    int		crlf		/* NEWSPUTS_CRLF, NEWSPUTS_NONE */
)
{
    int		ml;		/* Message length */
    char	*wbp;		/* Write buffer pointer */
    int		wbs;		/* Write buffer size */
    int		ws;		/* Write size */
    int		nnbs;		/* New output buffer size */
    static	char *nbp;	/* Static output buffer */
    static	int nbs;	/* Output buffer size */

    dprintf(DEBUG_FUN,(debugout,
	"NewsPuts(N=%p,mp=%p,msize=%d,crlf=%d)\n",
	N,
	mp,
	msize,
	crlf
    ));

    ml = msize >= 0 ? msize : strlen(mp);

    if(debugNetDump == TRUE)
    {
	LogMsg("NewsPuts");
	LogDump((u_char *)mp,msize,LD_HEX|LD_ASCII);
    }

    /*
     * If we are adding the CRLF then transfer to the internal buffer first
     * Otherwise, write directly from the callers buffer
     */
    if(crlf == NEWSPUTS_CRLF)
    {
	/*
	 * Build output buffer.
	 * Expand if necessary.
	 * Concatonate message and CRLF
	 */
	nnbs = ml+2;
	if(nnbs > nbs)
	{
	    if(nbp)
	    {
		nbp = (char *)realloc(nbp,nnbs);
	    }
	    else
	    {
		nbp = (char *)malloc(nnbs);
	    }
	    if(!nbp)
	    {
		LogError("Out of memory in NewsPuts()");
		return(-1);
	    }
	    nbs = nnbs;
	}
	memcpy(nbp,mp,ml);
	nbp[ml++] = '\r';
	nbp[ml++] = '\n';

	wbp = nbp;
    }
    else
    {
	wbp = mp;
    }

    /*
     * Write message to news server
     */
    wbs = ml;
    signal(SIGALRM,NewsAlarm);
    while(wbs > 0) {
	alarm(SELECT_TIMEOUT);
	if((ws = write(N->fd,wbp,wbs)) < 0)
	{
	    LogError("Write to news stream");
	    alarm(0);
	    signal(SIGALRM,SIG_DFL);
	    return(-1);
	}
	wbp += ws;
	wbs -= ws;
    }
    alarm(0);
    signal(SIGALRM,SIG_DFL);

    return(0);
}

static void
NewsAlarm(
    int		sig
)
{
    if (sig == SIGALRM)
    {
	LogMsg("NEWSALARM");
	alarm(SELECT_TIMEOUT);
    }
    else
    {
	sprintf(logBuf,"NEWSALARM: signal %d",sig);
	LogMsg(logBuf);
    }
}

/*
 * Read a line or an article from the news server
 *
 * Returns 0 if ok, 1 if EOF else -1
 * For NEWSGETS_ARTICLE EOF is the normal return.  0 means the buffer
 * filled up without finding the end of the article
 *
 */
#define	NG_IGNORECHAR	(mp--, msize++, trs--)
int
NewsGets(
    NEWS	*N,		/* News stream */
    char	*mbuf,		/* Message buffer */
    int		msize,		/* Size of mbuf */
    int		*gsize,		/* Size read (if not NULL) */
    int		gtype		/* NEWSGETS_LINE, NEWSGETS_ARTICLE */
)
{
    char	*mp;		/* Pointer into buffer */
    char	*mpc;		/* Pointer for copy */
    int		rs;		/* Read size */
    int		trs;		/* Total read size */
    int		goteof;		/* EOF flag */
    char	*nbp;		/* News buffer pointer */
    char	*nbs;		/* News buffer size */
    char	c;		/* Current char */
    char	lastchar;	/* Last character */
    static char	nextterm;	/* Next terminator char to expect */
    fd_set	rmask;			/* select mask */
    struct	timeval	tval;		/* select timeout */
    int		selres;			/* select result */

    dprintf(DEBUG_FUN,(debugout,
	"NewsGets(N=%p,mbuf=%p,msize=%d,gsize=%p,gtype=%d(%s))\n",
	N,
	mbuf,
	msize,
	gsize,
	gtype,
	gtype==NEWSGETS_LINE ?
	    "line" :
	    gtype==NEWSGETS_ARTICLE ?
		"article" :
		"?"
    ));

    FD_ZERO(&rmask);
    FD_SET(N->fd,&rmask);

    /*
     * Get characters from the news stream till we hit a <EOL>
     * or we fill the buffer (leaving room for terminating null)
     * We leave one extra space for expanding single character line
     * terminators.
     */
    goteof = trs = 0;
    c = lastchar = 0;
    for(mp = mbuf;msize > 2;)
    {
	/* 
	 * Read from stream if buffer empty
	 */
	if(N->nsbs == 0)
	{
	    tval.tv_sec = SELECT_TIMEOUT;
	    tval.tv_usec = 0;

	    selres = select(N->fd+1,&rmask,0,0,&tval);

	    /*
	     * Socket timeout.
	     */
	    if (selres == 0)
	    {
		dprintf(DEBUG_NONE,(debugout,
		    "TIMEOUT: Connection inactive %d seconds\n",
		    SELECT_TIMEOUT
		));
		return(-1);
	    }
	    /*
	     * Error from select
	     */
	    if (selres < 0)
	    {
		if(errno != EINTR)
		{
		    LogError("select()");
		}
		return(-1);
	    }
	    /*
	     * We're only waiting on one thing...
	     */
	    rs = read(N->fd,N->nsb,sizeof(N->nsb));
	    if(rs < 0)
	    {
		LogError("Error reading the news stream");
		return(-1);
	    }
	    if(rs == 0)
	    {
		dlogmsg(DEBUG_ACTIVE,"EOF from news stream");
		return(-1);
	    }

	    if(debugNetDump == TRUE)
	    {
		LogMsg("NewsGets:read");
		LogDump((u_char *)N->nsb,rs,LD_HEX|LD_ASCII);
	    }

	    N->nsbp = N->nsb;
	    N->nsbs = rs;
	}

	/*
	 * Fullfill request from buffer
	 */
	N->nsbs--;
	msize--;
	trs++;
	lastchar = c;
	c = (*mp++ = *N->nsbp++);
	if (c == 0)
	{
	    /*
	     * Ignore nulls
	     */
	    NG_IGNORECHAR;
	    continue;
	}
#if NG_EOL == 1
	/*
	 * Ignore all \r's if EOL type 1 processing
	 */
	if ( c == '\r' )
	{
	    NG_IGNORECHAR;
	    continue;
	}
#else
#if NG_EOL == 0
	/*
	 * Type 0 processing (anything goes)
	 * Ignore the matched pair (\r or \n) if combination
	 * terminator.
	 */
	if (nextterm)
	{
	    if( c == nextterm )
	    {
		/*
		 * Ignore the second char of a two char terminator
		 */
		nextterm = 0;
		NG_IGNORECHAR;
		continue;
	    }
	    /*
	     * Single character terminator.  Just keep going
	     */
	    nextterm = 0;
	}
#endif
#endif
	if( NG_ISTERM(c) )
	{
#if NG_EOL == 0
	    /*
	     * Remember the next character to ignore
	     * (Not always used depending on EOL processing type)
	     */
	    if (c == '\n')
	    {
		nextterm = '\r';
	    }
	    else
	    {
		nextterm = '\n';
	    }
#endif
	    if(gtype == NEWSGETS_LINE)
	    {
		/*
		 * Strip the terminator if getting a line
		 */
		NG_IGNORECHAR;
		*mp = '\0';
		break;
	    }
	    /*
	     * Force in the standard EOL (\r\n)
	     */
	    mp[-1] = '\r';
	    mp[0]  = '\n';
	    trs++;
	    mp++;
	    msize--;
	    /*
	     * Set flag and break if .<EOL> found
	     * otherwise, break on \n if getting just one line
	     */
	    if(
		trs >= 3 &&
		mp[-3] == '.' &&
		( trs == 3 || mp[-4] == '\n' )
	    )
	    {
		goteof = 1;
		break;
	    }
	    continue;
	}
#if NG_EOL == 2
	if (c == '\n' || c == '\r')
	{
	    /*
	     * Ignore any lone terminators if type 2
	     */
	    NG_IGNORECHAR;
	}
#endif
    }

    /*
     * Terminate the buffer and
     * return the total size (not including the null)
     */
    *mp = '\0';
    if(gsize) *gsize = mp-mbuf;

    if(debugNetDump == TRUE)
    {
	LogMsg("NewsGets");
	LogDump((u_char *)mbuf,mp-mbuf,LD_HEX|LD_ASCII);
    }

    /*
     * Return EOF if terminator
     */
    return(goteof);
}

/*
 * Open up an outgoing connection
 */
NEWS *
NewsOpen(
    char	*newshost,	/* hostname */
    int		port		/* host port */
)
{
    struct	hostent	*h;	/* host entry */
    char	*hp;		/* current addr from h */
    int		hn;		/* current addr num in h */
    int		ns;		/* news socket */
    NEWS 	*nss;		/* news stream struct */
    struct	sockaddr_in sin;

    /*
     * Lookup host ip address
     */
    if(!(h = gethostbyname(newshost))) {
	sprintf(logBuf,"Address lookup failed for %s",newshost);
	LogMsg(logBuf);
	return((NEWS *)0);
    }

    /*
     * Open connection to the news server
     */
    ns = -1;
    for(hn=0; hp=h->h_addr_list[hn]; hn++)
    {
	/*
	 * Create socket--Fatal error if failure
	 */
	if((ns = socket(AF_INET, SOCK_STREAM, 0))<0) {
	    LogMsg("Couldn't create socket to news server");
	    return((NEWS *)0);
	}

	/*
	 * Build socket name
	 */
	memset((char *)&sin, 0, sizeof(sin));
	sin.sin_family = h->h_addrtype;
	memcpy((char *)&sin.sin_addr.s_addr, hp, h->h_length);
	sin.sin_port = htons(port);

	/*
	 * Connect to socket
	 */
	if(connect(ns,(struct sockaddr *)&sin, sizeof(sin))) {
	    sprintf(
		logBuf,
		"Couldn't connect to port %d of %s (%s)",
		ntohs(sin.sin_port),
		h->h_name,
		inet_ntoa(sin.sin_addr)
	    );
	    LogError(logBuf);
	    close(ns);
	    ns = -1;
	    continue;
	}
	/*
	 * Success--Break out of loop
	 */
#ifdef DEBUG
	sprintf(
	    logBuf,
	    "News connection to port %d of %s (%s)",
	    ntohs(sin.sin_port),
	    h->h_name,
	    inet_ntoa(sin.sin_addr)
	);
	LogMsg(logBuf);
#endif
	break;
    }
    if(ns < 0) {
	LogMsg("Couldn't connect to news server");
	return((NEWS *)0);
    }

    /*
     * Allocate and initialize news stream structure
     * Returns null value if calloc fails
     */
    if( (nss = (NEWS *)calloc(1,sizeof(NEWS))) == NULL)
    {
	LogMsg("Out memory allocating newstream");
    }
    else
    {
	nss->fd = ns;
	nss->nsbp = nss->nsb;
	nss->nsbs = 0;
    }
    return(nss);
}

/*
 * Get our host name
 */
void
HostName(void)
{
    strcpy(hostName,"news");
    gethostname(hostName,sizeof(hostName));
    hostName[sizeof(hostName)-1] = '\0';
    dprintf(DEBUG_STATE,(debugout,"Our hostname is %s\n",hostName));
}
