/***************************************************************************
 *						INVESTMENT INTELLIGENCE SYSTEMS CORPORATION
 *
 *									PROPRIETARY DATA
 *
 *		THIS DOCUMENT CONTAINS TRADE SECRET DATA WHICH IS THE PROPERTY OF
 *		INVESTMENT INTELLIGENCE. THIS DOCUMENT IS SUBMITTED TO RECIPIENT IN
 *		CONFIDENCE. INFORMATION CONTAINED HEREIN MAY NOT BE USED, COPIED OR
 *		DISCLOSED IN WHOLE OR IN PART EXCEPT AS PERMITTED BY WRITTEN AGREEMENT
 *		SIGNED BY AN OFFICER OF INVESTMENT INTELLIGENCE SYSTEMS CORPORATION
 *
 *		THIS MATERIAL IS ALSO COPYRIGHTED AS AN UNPUBLISHED WORK UNDER
 *		SECTIONS 104 AND 408 OF TITLE 17 OF THE UNITED STATES CODE.
 *		UNAUTHORIZED USE, COPYING OR OTHER REPRODUCTION IS PROHIBITED BY LAW.
 *
 *		Title:	WZListen.c
 *		Sccsid:	@(#)WZListen.c	3.3.1.4	09/08/98	14:46:55
 *		Author:	Phil Shotton
 *		Created:	Wed 26 Mar 18:12:45 1997
 *
 ***************************************************************************/

/*
 * Boundin to listen for incoming requests
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#define numfuncs 4
#include "Boundin.h"

#define MAINPROG /* so everything in wzlisten.h gets pulled in */
#include "wzlisten.h"

int tt_errornum = 0 ;				/* last error */
static TT_CONSTRP connarray[MAX_CONNS];
static int debuglvl = 0;

char * tt_errstr(int);
static char our_id[255];
SECURE authlist;

STATIC BDROUT rout =
{
    numfuncs, XFDEF(ExitFunc),
    {
	{XFDEF(DebugMode), "\011DebugMode", 1 | NORMAL},
	{XFDEF(Password), "\010Password", 1 | NORMAL},
	{XFDEF(Mode), "\004Mode", 1 | NORMAL},
	{XFDEF(Hosts), "\005Hosts", 2 | NORMAL}
    }
};

/* The following function is used in the Windows environment. */
/* It is required to build a dynamic link library (DLL). */
#if defined(WIN32)
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}
#endif

/* -------------------------------------------------------------- */
/*  Point of entry */
BDMAINFUNC(BoundinCalls * calls)
{
    int i;
    char * port;

#ifdef TT_DEBUG
    printf("InitFunc called\n");
#endif /* TT_DEBUG */

    authlist.uid = geteuid();
    authlist.gid = -1;
    authlist.password = NULL;
    authlist.hosts = NULL;
    authlist.hnum = 0;
    
    addhost("localhost");

    for(i=0;i<MAX_CONNS;i++)
	connarray[i]=NULL;

    if(NULL == (port = getenv("WZL_PORT")))
	port = TT_COMMPORT;

    i = tt_listen(port,TT_MAXCMDS,TT_COMEVENT);

#ifdef TT_DEBUG
    printf("command listen, returns %d, %d (%s)\n",
	   i, tt_errornum,tt_errstr(tt_errornum));
#endif /* TT_DEBUG */

    return &rout;
}

/*
 * DebugMode - takes integer debug value, returns previous value
 */
void DebugMode(PVAL pret, PVAL parg)
{
    pret->val.numeric = debuglvl;
    if(NUMERIC == parg->flag)
    {
	debuglvl = (int)parg->val.numeric;
    }
    if(debuglvl)
	fprintf(stderr,"Set Debug Level to %d\n",debuglvl);
}


void callback(void * clientdata, INT * fdp, INPUTID * idp)
{
    TT_CONSTRP connp;
    char temp[300];

#ifdef TT_DEBUG
    printf("callback(%d,%d,%d)\n",(int)clientdata,
	   (int)*fdp, (int)*idp);
#endif /* TT_DEBUG */

    xfRemoveInput(* idp);

    if(0 > find_conn((int)clientdata,&connp))
	return;
    connp->xid = -1;

    if(strcmp(connp->eventname,TT_COMEVENT))
    {
	return;
    }
    tt_do_int_command((int)clientdata,connp);
    return;
}

void ExitFunc(PVAL pret,PVAL parg)
{
    int i;
    PHOST hl;
    
#ifdef TT_DEBUG
    printf("ExitFunc called\n");
#endif /* TT_DEBUG */

    for(i=0; i < MAX_CONNS ; i++)
    {
	TT_CONSTRP connp;
	if(connp = connarray[i])
	{
#ifdef TT_DEBUG
	    printf("Freeing up connection to %s\n",connp->hostname);
#endif /* TT_DEBUG */
	    if(connp->xid != -1)
	    {
#ifdef TT_DEBUG
		printf("Removing input %d ...\n", connp->xid);
#endif /* TT_DEBUG */
		xfRemoveInput(connp->xid);
	    }
	    (void)close(connp->fd);
	    free(connp);
	    connarray[i]=NULL;
	}
    }
    for(hl = authlist.hosts; hl ;)
    {
	PHOST tmp;
	tmp = hl->nxt;
	
	if(hl->name)
	    free(hl->name);
	free(hl);
	hl = tmp;
    }
    if(authlist.password)
	free(authlist.password);
    authlist.password = NULL;
    authlist.hosts = NULL;
    authlist.hnum = 0;
}

void Password(PVAL pret,PVAL parg)
{
    CHAR pass[255];
    static STR opass[255];

    if(STRING != parg->flag)
    {
	pret->flag = ERR;
	pret->val.err = ARGBAD;
	return;
    }

    opass[0] = 0;
    xfSetString(pret, opass);
    
    if(authlist.password)
    {
	c2pcopy((PCHAR)authlist.password, opass);
	free(authlist.password);
    }
    
    authlist.password = NULL;
    
    p2ccopy(parg->val.string, pass);
    if(strlen((char*)pass))
	authlist.password = strdup((char*)pass);
    
    if(debuglvl)
    {
	fprintf(stderr,"Set Password to '%s'\n",pass);
    }
    return;
}

void Mode(PVAL pret,PVAL parg)
{
    char mode[255];
    int i;
    
    if(STRING != parg->flag)
    {
	pret->flag = ERR;
	pret->val.err = ARGBAD;
	return;
    }

    if (authlist.uid < 0 && authlist.gid < 0)
	pret->val.numeric = 3;
    else if (authlist.uid < 0)
	pret->val.numeric = 2;
    else
	pret->val.numeric = 1;

    for (i = 0; i < (int)parg->val.string[0]; i++)
	mode[i] = toupper(parg->val.string[i+1]);

    mode[i] = 0;

    if(!strcmp("USER",mode))
    {
	authlist.uid = geteuid();
	authlist.gid = -1;
    }
    else if(!strcmp("GROUP",mode))
    {
	authlist.gid = getegid();
	authlist.uid = -1;
    }
    else if(!strcmp("OTHER",mode))
    {
	authlist.uid = -1;
	authlist.gid = -1;
    }
    else
    {
	pret->flag = ERR;
	pret->val.err = ARGBAD;
	return;
    }
    
    if(debuglvl)
    {
	fprintf(stderr,"Set Authorisations to '%s'\n",mode);
    }
    return;
}

void Hosts(PVAL pret,PVAL parg)
{
    char host[255];
    int ret;
    unsigned short dims[3];
    PHOST hp;
    
    if(STRING != parg[0].flag || NUMERIC != parg[1].flag)
    {
	pret->flag = ERR;
	pret->val.err = ARGBAD;
	return;
    }

    p2ccopy(parg->val.string, (PCHAR)host);

    if (parg[1].val.numeric == 0)
	ret = delhost(host);
    else
    {
	delhost(host);
	ret = addhost(host);
    }
    
    if (!ret)
    {
	pret->flag = ERR;
	pret->val.err = LOOKUPFAILED;
	return;
    }

    dims[0] = authlist.hnum;
    dims[1] = 2;
    if(!xfNewArray(pret, 2, dims))
	return;
    for (ret = 0, hp = authlist.hosts;
	 ret < authlist.hnum; hp = hp->nxt, ret++)
    {
	STR str[255];
	struct in_addr in;
	
	dims[0] = ret;
	dims[1] = 0;

	c2pcopy((PCHAR)hp->name, str);
	xfSetArrayStrEl(pret, dims, str);

	dims[1] = 1;
	in.s_addr = htonl(hp->addr);
	c2pcopy((PCHAR)inet_ntoa(in), str);
	xfSetArrayStrEl(pret, dims, str);
    }
}

int delhost(char * host)
{
    int ret;
    PHOST ph;

    for (ph = authlist.hosts; ph; ph = ph->nxt)
	if(!strcmp(ph->name, host))
	{
	    if (ph->nxt)
		ph->nxt->prv = ph->prv;
	    if (ph->prv)
		ph->prv->nxt = ph->nxt;
	    else
		authlist.hosts = ph->nxt;
	    free(ph->name);
	    free(ph);
	    authlist.hnum--;
	    return 1;
	}
    return 0;
}

int addhost(char * host)
{
    struct hostent * hst;
    PHOST he;

    if (NULL == (hst = gethostbyname(host)))
	return 0;

    if(NULL == (he = (PHOST)calloc(sizeof(HOST),1)))
	return 0;

    he->name = strdup(host);	
    he->addr = ntohl(((struct in_addr *)(hst->h_addr))->s_addr);

    he->nxt = authlist.hosts;    
    if(authlist.hosts)
	authlist.hosts->prv = he;
    authlist.hosts = he;
    authlist.hnum++;
    return 1;
}

/* ============================================================ */
/* ============================================================ */
/* main function entry points */

/*
   tt_listen(port, numcons, eventname) : create a socket to listen on for
       incoming connection
   args: int port - port to listen on
        int numcons - max number of connections to handle
	char * eventname - name of event to generate when activity
   returns: int index into listen structure array or errcode
*/
int tt_listen(char * port, int numcons, char * eventname)
{
    TT_CONSTRP connp;
    int idx;
    struct servent * svc;
    struct hostent * hst;
    struct sockaddr_in addr_in;
    int on,nport;

    tt_errornum = 0;

    if(NULL == (connp = (TT_CONSTRP)calloc(sizeof(TT_CONSTR),1)))
	return tt_errornum = TT_NOMEM;
	
    if( isallnum(port))
	nport=atoi(port);
    else
    {
	if (NULL == (svc = getservbyname(port,"tcp")))
	{
	    free(connp);
	    return tt_errornum = TT_NOSVC;
	}
	else
	    nport = svc->s_port;
    }
    
    if (0 > (connp->fd = socket(AF_INET,SOCK_STREAM,0)))
    {
	free(connp);
	return tt_errornum = TT_NOSOCK;
    }
    
    on = 1;

    if (setsockopt(connp->fd, SOL_SOCKET, SO_REUSEADDR,
		   (char*)&on, sizeof(on)))
    {
	free(connp);
	return tt_errornum = TT_SOCKERR;
    }
    
    if(0 > fcntl(connp->fd, F_SETFL, O_NDELAY))
	perror("fcntl");

    memset((char*)&addr_in,0,sizeof(addr_in));
    addr_in.sin_port = nport;
    connp->port = nport;
    addr_in.sin_family = AF_INET;
    addr_in.sin_addr.s_addr = INADDR_ANY;

    if(bind(connp->fd, (struct sockaddr *)&addr_in, sizeof(addr_in)))
    {
	free(connp);
	return tt_errornum = TT_NOBIND;
    }

    if(listen(connp->fd,numcons))
    {
	free(connp);
	return tt_errornum = TT_NOCONN;
    }

    connp->numcons = numcons;

    if(0 > (idx = add_conn(connp, "", eventname)))
    {
	free(connp);
	return tt_errornum = TT_INTERNAL;
    }

    connp->xid = xfAddInput(connp->fd, INPUT_MASK_READ, callback, (void *)idx);
    connp->status = TT_LISTWAIT;
    connp->lsterr = 0;
    return idx;
}

/*
   tt_accept(idx,eventname) : accept a connection on listen port
   args: int idx - listen struct index
         char * eventname - name of event to send when activity
   returns: int index into connection structure array or errcode
*/
int tt_accept(int connection,char * eventname)
{
    TT_CONSTRP connp, listp;
    int idx,aidx;
    struct hostent * hst;
    struct sockaddr_in addr_in;
    int slen;
    int on,nport;
    char hostname[MAX_HOSTNAME_SZE];
    tt_errornum = 0;

    if( 0 > (idx = find_conn(connection,&listp)))
	return idx;

    listp->xid = xfAddInput(listp->fd, INPUT_MASK_READ, callback, (void *)idx);

    if(listp->status != TT_LISTWAIT)
	return TT_NOLIST;

    if(NULL == (connp = (TT_CONSTRP)calloc(sizeof(TT_CONSTR),1)))
	return tt_errornum = TT_NOMEM;

    memcpy(connp,listp,sizeof(TT_CONSTR));

    memset((char*)&addr_in,0,sizeof(addr_in));

    slen = sizeof(addr_in);

    if(0 > (connp->fd = accept(listp->fd,(struct sockaddr *)&addr_in,&slen)))
    {
	perror("accept");
	free(connp);
	return tt_errornum = TT_NOCONN;
    }
    connp->addr = ntohl(addr_in.sin_addr.s_addr);
    
    if(NULL != (hst = gethostbyaddr((void *)&addr_in.sin_addr.s_addr,
				    sizeof(struct in_addr), AF_INET)))
	strcpy(hostname, hst->h_name);
    else
	hostname[0] = '\0';

    on = 1;
#ifdef DONT_LINGER
    if (setsockopt(connp->fd,SOL_SOCKET,SO_LINGER,(char*)&on,
		   sizeof(on /* struct linger */)))
    {
	free(connp);
	perror("setsockopt");
	return tt_errornum = TT_NOCONN;
    }
#endif
    if(0 > (aidx = add_conn(connp, hostname, eventname)))
    {
	free(connp);
	return tt_errornum = TT_INTERNAL;
    }

    if(0 > fcntl(connp->fd, F_SETFL, O_NDELAY))
	perror("fcntl");

    connp->xid= xfAddInput(connp->fd, INPUT_MASK_READ, callback, (void *)aidx);
    connp->status = TT_CONNECTED;
    connp->lsterr = 0;
    return aidx;
  
}

/*
   tt_read(connection, result, count, termchars, tcnt) 
   	: get data from a connection
   args: int connection - index into connection array
	 char * result - buffer for result
         int count - max number of characters to return
	 char *termchars - characters to return on
	 int tcnt - number of termchars
   returns: int num chars read or error code
*/
int tt_read(int connection, char * result, int count, char * termchars, int tcnt)
{
    int br = 0;
    TT_CONSTRP connp;

    tt_errornum = 0;

    if(0 > (br = find_conn(connection, &connp)))
	return br;

    if(TT_CONNECTED != connp->status)
	return TT_NOTCONNECTED;

    tt_errornum = 0;

    if(tcnt)
    {
	int i;
	
	if(0 > fcntl(connp->fd, F_SETFL, &i))
	    perror("fcntl");
	else
	{
	    i &= (~O_NDELAY);
	    if(0 > fcntl(connp->fd, F_SETFL, i))
		perror("fcntl");
	}
	for(i = 0; i< count; i++)
	{
	    int j;
	    br = read(connp->fd, result, 1);

	    if(0 >= br)
		break;

	    for(j = 0; j < tcnt; j++)
		if(termchars[j] == *result)
		    return i+1;

	    result++;
	}

    }
    if(0 > fcntl(connp->fd, F_SETFL, O_NDELAY))
	perror("fcntl");
    br = read(connp->fd, result, count);
    if(!br)	      /* 0 on read indicates connection lost */
    {
	connp->status = TT_NOTCONNECTED;
	br = connp->lsterr = tt_errornum = TT_CONNCLOSED;
	if(debuglvl)
	    fprintf(stderr,"Connection to %s closed\n",connp->hostname);
	del_conn(connp);
	return br;
    }
    if((br < 0) && (EWOULDBLOCK == errno)) /* no data to read */
	br = 0;

    if(br < 0)
	tt_errornum = connp->lsterr = br;
    else
    {
	result[br]=0;
    }

    return br;
}


char * decode(char * data)
{
    char * p;
    static char temp[255];
    
    p = temp;

    for(;*data;data++)
    {
	if(32< *data)
	    *p++ = *data;
	else
	{
	    *p++ = '<';
	    *p++ = '0' + (int)((*data)/10);
	    *p++ = '0' + *data - (10 * ((int)((*data)/10)));
	    *p++ = '>';
	}
    }
    *p = 0;
    return temp;
}


/* 
   tt_errstr(code) : return message string corresponding to code
   args: int code - error code returned from other functions
*/
char * tt_errstr(int t)
{
    static char tmp[200];

    t = 0 - t;

    t = t + TT_NOERROR;

    if( (t <  0) || (t > (TT_NOERROR - TT_LASTERROR)))
    {
	sprintf(tmp,"Error %d Unrecognized", 0 - (TT_NOERROR - t));
	return tmp;
    }
    return TT_ERRORMSG[t];
}

/*
   This handles the internal command listen port */
void tt_do_int_command(int conidx,TT_CONSTRP connp)
{
    int res;
    int flg;
    char tc[3];
    char buff[TT_MAXCMD_LEN+1];

    tc[0]='\n';
    tc[1]='\r';
    tc[2]='\0';

    if(connp->status == TT_LISTWAIT)		/* listen port? */
    {
	TT_CONSTRP newcon;
	res = tt_accept(conidx,TT_COMEVENT);
	if(0 > find_conn(res, &newcon))
	    return;

	newcon->status = TT_IDENTIFY;
	
	if(debuglvl && (res >= 0))
	{
	    fprintf(stderr,"Connection initiated from %s\n",
		    newcon->hostname);
	}
	return;
    }
    /* OK we got a read ; go get the command. This doesn't have to
       be limited to a pascal string */

    if ((flg = connp->status) == TT_IDENTIFY)
	connp->status = TT_CONNECTED; /* need this or read fails */
	
    if( 0 > (res = tt_read(conidx, connp->buff + connp->bidx,
		  TT_MAXCMD_LEN - connp->bidx, tc, 3)))
	return;

    if (flg == TT_IDENTIFY)
	connp->status = TT_IDENTIFY;

    if(connp->xid == -1)
	connp->xid = xfAddInput(connp->fd,INPUT_MASK_READ,
				callback, (void *)conidx);
    connp->bidx += res;
    connp->buff[connp->bidx] = 0;

    while(connp->bidx)
    {
	char * p;
	
	if(NULL==(p = strpbrk(connp->buff,tc)))
	    return;
    
	*p++ = '\0';

	strcpy(buff,connp->buff);
	connp->bidx -= p - connp->buff;
	memmove(connp->buff, p, connp->bidx);

	if(connp->status == TT_IDENTIFY)
	{	
	    if (!auth_ok(buff, connp))
	    {
		close(connp->fd);
		del_conn(connp);
		return;
	    }
	    connp->status = TT_CONNECTED;
	    buff[0] = 0;
	}

	if(strlen(buff))
	{
	    if(debuglvl)
	    {
		fprintf(stderr,"Read command '%s' from %s\n",
			buff, connp->hostname);
	    }
#ifdef TT_DEBUG
	    printf("calling ssCommand with [%s]\n",buff);
#endif /* TT_DEBUG */
	    xfssCommand((PSTR)buff);
	}
	
    }
}

int auth_ok(char * buff, TT_CONSTRP connp)
{
    char *magic, *password, *tmp1, *tmp2;
    int gid,uid;
    PHOST hosts;
    
    if(debuglvl)
    {
	fprintf(stderr,"Authentication check '%s' from %s\n",
		buff, connp->hostname);
    }

#ifdef TT_DEBUG
    printf("Identity read: '%s' [we're '%s:%d:%d:%s']\n",
	   buff, WZ_MAGIC, authlist.uid, authlist.gid, authlist.password);
#endif /* TT_DEBUG */

    if ((NULL == (magic = strtok(buff, ":"))) ||
	(NULL == (tmp1 = strtok(NULL,":"))) ||
	(NULL == (tmp2 = strtok(NULL,":"))))
    {
	if(debuglvl)
	{
	    fprintf(stderr,"Bad authentication string from %s\n",
		    connp->hostname);
	}
	return 0;
    }
    
    password = strtok(NULL,":");
    uid = atoi(tmp1);
    gid = atoi(tmp2);

    if ((authlist.uid >= 0 && authlist.uid != uid) ||
	(authlist.gid >= 0 && authlist.gid != gid) ||
	strcmp(magic, WZ_MAGIC) ||
	(authlist.password && strcmp(authlist.password, password)))
    {
	if(debuglvl)
	{
	    fprintf(stderr,"Authentication failed from %s\n",
		    connp->hostname);
	}
	return 0;
    }
/* authentication checks succeed, all we need to do now is check host! */

#ifdef TT_DEBUG
    printf("incoming address %d:%d:%d:%d\n",
	   (0xFF000000 & connp->addr)>>24,
	   (0x00FF0000 & connp->addr)>>16,
	   (0x0000FF00 & connp->addr)>>8,
	   (0x000000FF & connp->addr));
#endif /* TT_DEBUG */
    for(hosts = authlist.hosts; hosts; hosts = hosts->nxt)
	if( connp->addr == hosts->addr)
	    return 1;
    
    return 0;
}

    
/* ============================================================ */
/* ============================================================ */
/* utility functions and data */

/*
   add_conn(connp, hostname, port, eventname) - add connection struct
   args: TT_CONSTRP connp - pointer to connection struct
         char * hostname - remote host name
	 int port - port number
	 char * eventname - name of event to raise on activity
   returns: int idx - index into connstr array else -1
*/
int add_conn(TT_CONSTRP connp, char * hostname, char * eventname)
{
    int i;

    /* should do this as a linked list, but do it as array for now */
    for(i=0; i < MAX_CONNS ; i++)
	if(NULL == connarray[i])
	{
	    connarray[i] = connp;
	    break;
	}

    if( MAX_CONNS <= i)
	return -1;

    strncpy(connp->hostname, hostname, MAX_HOSTNAME_SZE);
    connp->hostname[MAX_HOSTNAME_SZE] = '\0';
    strncpy(connp->eventname, eventname, MAX_EVENTNAME_SZE);
    connp->eventname[MAX_EVENTNAME_SZE] = '\0';

    return i;
}

void del_conn(TT_CONSTRP connp)
{
    int i;

    for (i=0; i < MAX_CONNS ; i++)
	if (connp == connarray[i])
	    break;

    if ((i >= MAX_CONNS) || connarray[i] == NULL)
	return;
    
    connarray[i] = NULL;
    if(connp->xid != -1)
    {
#ifdef TT_DEBUG
	printf("Removing input %d ...\n", connp->xid);
#endif /* TT_DEBUG */
	xfRemoveInput(connp->xid);
    }
    free(connp);    
}


/*
   int find_conn(int idx, TT_CONSTRPP connp) :
       put the connection structure for handled number idx into connp; 
       return negative error code if not found else 0
*/
int find_conn(int idx, TT_CONSTRPP connp)
{
    if(idx >= MAX_CONNS)
	return TT_BADARG;

    if(NULL == (*connp = connarray[idx]))
	return TT_NOCONN;
    return 0;
}

/*
   int isallnum(char * name) - check if string is all numerics (incl '.')
   returns 1 (all numerics) or 0
*/
int isallnum(char * name)
{
    for(;*name;name++)
	if ((!isdigit(*name)) && ('.'  != *name))
	    return 0;
    return 1;
}
