/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

# include "epp.h"

RCSID("$Id: eppsup.c 466 2001-03-01 10:46:44Z hbb $")

/*
 * global stuff
 */
char	*progname;	/* program name */
int	verbose;	/* command line flag */
u_int	rcv_enable;	/* receiver is enabled */
eppcomm_t *comm;	/* shared memory */
int	fd;		/* input file */

filter_t f;		/* filter expression */

u_char	xbuf[2048];	/* transmit buffer */
u_int	xlen;		/* frame size */

/*
 * local stuff
 */
int	change;		/* request from p11 to change state */
int	do_reset;	/* need to reset */
u_int	reset_count;
sigset_t allmask;	/* all used signals */



void	onterm(int);	/* SIGTERM handler */
void	onhup(int);	/* SIGHUP handler */
void	onusr1(int);	/* SIGUSR1 handler */
void	oninfo(int);	/* SIGINFO handler */

void	do_change(void);
void	do_p11_input(void);
void	do_net_input(void);

void	parse_filter(void);

void	process_setup(void);
void	process_iloop(void);
void	process_eiloop(int);



/*
 * main processing loop
 * select p11-pipe
 */
void
loop()
{
	fd_set	iset, rset;
	epp_t	c;
	int	ret;

	FD_ZERO(&iset);
	FD_SET(0, &iset);
	FD_SET(fd, &iset);

	c.len = 0;
	c.type = EPP_INIT;
	c.reset_count = getpid();
	if(write(0, &c, sizeof(c)) != sizeof(c))
		panic("initialisation write failed: %s", strerror(errno));

	for(;;) {
		while(change)
			do_change();
		rset = iset;
		if((ret = select(fd + 1, &rset, NULL, NULL, NULL)) < 0) {
			if(errno != EINTR)
				panic("select: %s", strerror(errno));
		} else if(ret > 0) {
			if(FD_ISSET(0, &rset))
				do_p11_input();
			if(FD_ISSET(fd, &rset))
				do_net_input();
		}
	}
}


/*
 * got request from p11 to change state
 *
 * Note:
 *	QNA does NOT reset the filter on soft-reset
 */
void
do_change()
{
	sigset_t oset;

	if(sigprocmask(SIG_BLOCK, &allmask, &oset))
		panic("sigprocmask: %s", strerror(errno));

	change = 0;

	if(do_reset) {
		verb(V_DFLT, 1, "reset %d\n", comm->reset_count);
		reset_count = comm->reset_count;
		do_reset = 0;
		reset();
	}
	if(rcv_enable != comm->rcv_enable) {
		rcv_enable = comm->rcv_enable;
		rcv_enable_change();
	}

	if(sigprocmask(SIG_SETMASK, &oset, NULL))
		panic("sigprocmask: %s", strerror(errno));
}


/*
 * input from p11
 */
void
do_p11_input()
{
	epp_t	c;
	int	ret;

	if(read(0, &c, sizeof(c)) != sizeof(c))
		panic("socket read error: %s", strerror(errno));
	xlen = c.len;
	if((ret = read(0, xbuf, xlen)) < 0)
		panic("socket read error: %s", strerror(errno));
	if((u_int)ret != xlen)
		panic("bad socket read: %d %u", ret, xlen);

	if(c.reset_count != reset_count) {
		verb(V_DFLT, 1, "p11 input dropped because reset counts don't match: %d %d\n",
			c.reset_count, reset_count);
		return;
	}

	switch(c.type) {

	case EPP_TRANSMIT:
		verb(V_DFLT, 1, "EPP_TRANSMIT %d.\n", xlen);
		process_eiloop(EPP_TRANSMIT);
		break;

	case EPP_ILOOP:
		verb(V_DFLT, 1, "EPP_ILOOP %d.\n", xlen);
		process_iloop();
		break;

	case EPP_ELOOP:
		verb(V_DFLT, 1, "EPP_ELOOP %d.\n", xlen);
		process_eiloop(EPP_ELOOP);
		break;

	case EPP_EILOOP:
		verb(V_DFLT, 1, "EPP_EILOOP %d.\n", xlen);
		process_eiloop(EPP_EILOOP);
		break;

	case EPP_SETUP:
		verb(V_DFLT, 1, "EPP_SETUP %d.\n", xlen);
		process_setup();
		break;

	default:
		panic("bad command from p11: %04x", c.type);
	}
}

/*
 * process extended internal loopback
 * packet is in xbuf, length in xlen
 */
void
process_eiloop(int xmt)
{
	u_short	status[2];

	status[0] = (xlen >= 04000) ? 064414 : 020414;
	status[1] = 010 * (xlen % 04000) + 0140;
	p11_write(EPP_XSTATUS, status, sizeof(status));

	if(xlen < 6) {
		status[0] = 070010;
		status[1] = (xlen & BD_RBLL) | ((xlen & BD_RBLL) << 8);
	} else if(xlen < 03000) {
		status[0] = 020000 | (xlen & BD_RBLH);
		status[1] = (xlen & BD_RBLL) | ((xlen & BD_RBLL) << 8);
		if((xmt == EPP_ELOOP || xmt == EPP_TRANSMIT) && xlen >= ETHMIN && xlen <= ETHMTU)
			transmit();
	} else {
		status[0] = 073001;
		status[1] = 000000;
		xlen = 02766;
	}

	if(xmt != EPP_TRANSMIT) {
		p11_write(EPP_RECEIVE, xbuf, xlen);
		p11_write(EPP_RSTATUS, status, sizeof(status));
	}
}

/*
 * process internal loopback
 * packet is in xbuf, length in xlen
 */
void
process_iloop()
{
	u_short	status[2];

	status[0] = (xlen >= 04000) ? 064414 : 020414;
	status[1] = 010 * (xlen % 04000) + 0140;
	p11_write(EPP_XSTATUS, status, sizeof(status));

	if(xlen == 6) {
		p11_write(EPP_RECEIVE, xbuf, xlen);
		if(apply_filter(xbuf)) {
			status[0] = 0200;
			status[1] = 0;
		} else {
			status[0] = 047600;
			status[1] = 071162;
		}
		p11_write(EPP_RSTATUS, status, sizeof(status));
	}
}


/*
 * process setup packet
 * packet is in xbuf, length in xlen
 */
void
process_setup()
{
	u_short	status[2];
	u_int	len;

	/*
	 * return transmit status
	 */
	status[0] = 020414;
	status[1] = 010 * xlen + 0140;
	p11_write(EPP_XSTATUS, status, sizeof(status));

	if(xlen >= 6 && xlen <= 0400) {
		/*
		 * packet size ok, loop back packet
		 */
		len = xlen;
		status[0] = 023600;
		parse_filter();
	} else {
		/*
		 * bad size, nothing looped back but status written
		 */
		len = 0;
		status[0] = 073410 + (xlen > 0400);
	}

usleep(500000);
	p11_write(EPP_RECEIVE, xbuf, len);

	status[1] = (xlen & BD_RBLL) | (xlen & BD_RBLL) << 8;
	p11_write(EPP_RSTATUS, status, sizeof(status));
}

/*
 * write command to p11
 */
void
p11_write(u_int type, void *p, u_int len)
{
	int	ret;
	epp_t	c;
	struct iovec iov[2];

	c.type = type;
	c.len = len;
	c.reset_count = reset_count;

	iov[0].iov_base = (caddr_t)&c;
	iov[0].iov_len = sizeof(c);
	iov[1].iov_base = (caddr_t)p;
	iov[1].iov_len = len;
	if((ret = writev(0, iov, 2)) < 0)
		panic("write error on fd 0: %s", strerror(errno));
	if((u_int)ret != sizeof(c) + len)
		panic("bad write error on fd 0: %d %u", ret, sizeof(c)+len);
}

/*
 * input from input
 */
void
do_net_input()
{
	u_char	*buf;
	u_int	got;
	u_short	status[2];
	int	more = 1;

	while(more) {
		if((got = read_input(&buf, &more)) == 0)
			continue;

		if(!rcv_enable) {
			verb(V_DFLT, 1, "%d byte frame dropped because receiver disabled\n", got);
			continue;
		}

		/*
		 * The DEQNA is not excact ether-neat with regard to the minimal
		 * frame length
		 */
# ifdef ether_neat
		if(got < ETHMIN) {
			verb(V_DFLT, 1, "%d byte frame dropped because frame too small\n", got);
			continue;
		}
# endif

		if(got > ETHMTU)
			got = ETHMTU;

		if(!input_filter(buf)) {
			verb(V_DFLT, 1, "%d byte frame dropped - no filter match\n", got);
			continue;
		}
		verb(V_DFLT, 1, "%d byte frame received\n", got);

		p11_write(EPP_RECEIVE, buf, got);

# ifndef ether_neat
		if(got < ETHMIN)
			got = ETHMIN;
# endif
		status[0] = (got - 60) & BD_RBLH;
		status[1] = (got - 60) & BD_RBLL;
		status[1] |= status[1] << 8;
		p11_write(EPP_RSTATUS, status, sizeof(status));
	}
}


/*
 * do address filtering on buffer
 */
int
apply_filter(u_char *buf)
{
	u_int	i;

	if(f.promisc || (f.allmulti && (buf[0] & 1)))
		return 1;

	for(i = 0; i < f.naddr; i++)
		if(!memcmp(buf, f.addr[i], 6))
			return 1;
	return 0;
}

/*
 * parse setup packet in xbuf/xlen
 */
void
parse_filter()
{
	u_char	*p;
	u_int	a, b, c;

	f.promisc = (xlen > 0200) && (xlen & 02);
	f.allmulti = (xlen > 0200) && (xlen & 01);
	f.naddr = 0;

	/*
	 * extract fully specified addresses
	 */
# define MIN(A,B) (((A) < (B)) ? (A) : (B))
	for(c = 0; c <= 0100; c += 0100) {
		if(xlen > 051 + c) {
			for(a = 0; a < MIN(7, xlen - 051 - c); a++) {
				p = f.addr[f.naddr++];
				for(b = 0; b < 060; b += 010)
					*p++ = xbuf[a + b + c + 1];
			}
		}
	}
# undef MIN

# if 0
	for(a = 0; a < f.naddr; a++)
		verb(V_DFLT, 1, "A[%2d] = %02x:%02x:%02x:%02x:%02x:%02x\n", a,
			f.addr[a][0], f.addr[a][1], f.addr[a][2],
			f.addr[a][4], f.addr[a][4], f.addr[a][5]);
# endif

	/*
	 * remove duplicates
	 */
	c = 0;
	while(c < f.naddr) {
		a = c + 1;
		while(a < f.naddr) {
			if(memcmp(f.addr[a], f.addr[c], 6) == 0) {
				if(a < f.naddr - 1)
					memcpy(f.addr[a], f.addr[a+1], 6 * (f.naddr - a - 1));
				f.naddr--;
			} else
				a++;
		}
		c++;
	}
# if 0
	for(a = 0; a < f.naddr; a++)
		verb(V_DFLT, 1, "B[%2d] = %02x:%02x:%02x:%02x:%02x:%02x\n", a,
			f.addr[a][0], f.addr[a][1], f.addr[a][2],
			f.addr[a][4], f.addr[a][4], f.addr[a][5]);
# endif

	/*
	 * call specific function
	 */
	set_filter();
}


/*
 * open shared memory
 *
 * file descriptor used for naming must be open on 3
 */
void
open_shmem()
{
	comm = (eppcomm_t *)mmap_shared_child(sizeof(eppcomm_t), 3);
# if 0
	close(3);
# endif
}

/*
 * initialize signals
 *
 * set global allmask to summ of all signals
 */
void
init_sigs()
{
	struct sigaction sa;

	sigemptyset(&allmask);
	sigaddset(&allmask, SIGUSR1);
	sigaddset(&allmask, SIGHUP);
	sigaddset(&allmask, SIGTERM);
	sigaddset(&allmask, SIGINFO);

	sa.sa_mask = allmask;
	sa.sa_flags = SA_RESTART;

	sa.sa_handler = onhup;
	if(sigaction(SIGHUP, &sa, NULL))
		panic("sigaction(SIGHUP) failed: %s", strerror(errno));

	sa.sa_handler = onterm;
	if(sigaction(SIGTERM, &sa, NULL))
		panic("sigaction(SIGTERM) failed: %s", strerror(errno));

	sa.sa_handler = onusr1;
	if(sigaction(SIGUSR1, &sa, NULL))
		panic("sigaction(SIGUSR1) failed: %s", strerror(errno));

	sa.sa_handler = oninfo;
	if(sigaction(SIGINFO, &sa, NULL))
		panic("sigaction(SIGINFO) failed: %s", strerror(errno));

	sa.sa_handler = SIG_IGN;
	if(sigaction(SIGINT, &sa, NULL))
		panic("sigaction(SIGINT) failed: %s", strerror(errno));
}

/*
 * SIGTERM
 * terminate
 */
void
onterm(int sig UNUSED)
{
	exit(0);
}


/*
 * SIGHUP
 * reset all state
 */
void
onhup(int sig UNUSED)
{
	change = 1;
	do_reset++;
}

/*
 * SIGUSR1
 * state change
 */
void
onusr1(int sig UNUSED)
{
	change = 1;
}



/*
 * SIGINFO
 */
# define P	buf+strlen(buf)
void
oninfo(int sig UNUSED)
{
	char	buf[50000];
	u_int	i;

	buf[0] = '\0';

	do_info(buf);

	sprintf(P, "  re=%d", rcv_enable);
	sprintf(P, "  reset_count=%d", reset_count);
	sprintf(P, "\n");

	sprintf(P, "  promisc=%d", f.promisc);
	sprintf(P, "  allmulti=%d", f.allmulti);
	sprintf(P, "  naddr=%d", f.naddr);
	sprintf(P, "\n");

	for(i = 0; i < f.naddr; i++) {
		sprintf(P, "  %02x:%02x:%02x:%02x:%02x:%02x",
			f.addr[i][0], f.addr[i][1], f.addr[i][2],
			f.addr[i][3], f.addr[i][4], f.addr[i][5]);
		if(i % 4 == 3)
			sprintf(P, "\n");
	}
	if(i % 4 != 0)
		sprintf(P, "\n");
	write(2, buf, strlen(buf));
}

/*
 * parse ethernet address
 */
void
parse_ether(u_char *e, char *buf)
{
	u_int	a[6];

	if(sscanf(buf, "%x:%x:%x:%x:%x:%x", a+0, a+1, a+2, a+3, a+4, a+5) != 6)
		panic("can't parse ethernet address '%s'", buf);
	e[0] = a[0];
	e[1] = a[1];
	e[2] = a[2];
	e[3] = a[3];
	e[4] = a[4];
	e[5] = a[5];
}

