/*-
 * Copyright (c) 1996, Trusted Information Systems, Incorporated
 * All rights reserved.
 *
 * Redistribution and use are governed by the terms detailed in the
 * license document ("LICENSE") included with the toolkit.
 */

/*
 *      Author: Kelly Djahandari, Trusted Information Systems, Inc.
*/

#include        <stdio.h>
#include        <ctype.h>
#include        <fcntl.h>
#include        <syslog.h>
#include        <sys/signal.h>
#include        <sys/ioctl.h>
#include        <sys/errno.h>
#include        <sys/socket.h>  
#include        <sys/types.h>  
#include        <sys/stat.h>  
#include        <netinet/in.h>  
#include        <sys/time.h>
#include 	<netdb.h>
#include 	<string.h>
#include	"mbone-gw.h"
#include 	"firewall.h"

static int 	acceptrule();
static void	accept_setdest();
static int	add_membership();
static int 	bind_udp_sock();
static int	cfg_check();
static int 	client_list_add();
static void 	client_list_remove();
static int 	disable_loopback();
static void 	drop_membership();
static int 	getauth();
static int 	get_minfo();
static int	get_sdr_sock();
static int	get_sdr_pkt_ttl();
static int 	get_ucast_port();
static int 	is_inside();
static int	lock_client_list_file();
static int	oktotalkto();
static int	port_lock();
static void	port_unlock();
static void 	read_inside_client_list();
static void 	read_drop_minfo();
static int 	reply_minfo();
static int	say();
static int	sayn();
static void 	send_error();
static int 	send_in();
static int 	send_out();
static int 	set_bufsize();
static int 	setup_mcast_sockets();
static int 	set_moptions();
static void	sig_alarm();

extern  int     errno;

/* Information from client */
static unsigned long Mcast_group;  /* Multicast group addr (network order)*/
static unsigned short Mcast_port;  /* Port to recv from outside (host order)*/
static unsigned short Ucast_port;  /* Port to send/recv to/from inside (h o)*/
static unsigned long inside_host;  /* addr of connected client (nw order)*/
static unsigned short Mcast_ttl;   /* ttl value */
static char	Mcast_appl[MAXAPPLSTR];/* Application name */
static char 	Mcast_user[MAXUSRSTR];/* login name of user on client */
static unsigned short Mcast_rtp;   /* RTP version used 
				(0 means no RTP Control channel used)
				(1 means RTPv1 or v0 used)
				(2 means RTPv2 used) */

/* data sockets */
static int	mcast_recv_sock = -1;  /* Socket to recv multicast datagrams */
static int	mcast_send_sock = -1;  /* Socket to send multicast datagrams */
static int	client_recv_sock = -1; /* Socket to recv client ucast */
static int	client_send_sock = -1; /* Socket to send client ucast */
static int	ic_sock = -1;	   /* Socket to send to other inside clients */

/* ctrl sockets */
static int	mcast_ctrl_recv_sock = -1;  /* Socket to recv mcast ctrl */
static int	mcast_ctrl_send_sock = -1;  /* Socket to send mcast ctrl */
static int	client_ctrl_recv_sock = -1; /* Socket to recv client ctrl */
static int	client_ctrl_send_sock = -1; /* Socket to send client ctrl */

static struct	sockaddr_in client_sin;		/* Client data sockaddr */
static struct	sockaddr_in client_ctrl_sin;	/* Client control sockaddr */


static char 		rladdr[512];/* clients nw name (dot notation) */
static char 		riaddr[512]; /* clients nw address (dot notation) */

static int		authneeded = 0;		/* -auth or -authall option */
static int		authsdrneeded = 0;	/* -authsdr option */
static char		authuser[128];
static int		timeout = PROXY_TIMEOUT;
static int		timelimit = 0;	/* Total time mbone-gw will run */
					/* 0 means no time limit */
static int		leaveflag = 0;  /* Flag to indicate need to exit */
					/* because time limit exceeded */
static  Cfg                     *confp;
static  char                    **validests = (char **)0;

#define FD0 0; 			/* Descriptor 0 */
static int	tcpfd = FD0; 	/* Descriptor for tcp connection with client */
				/* Currently equals 0 (from inetd) */


#define MAXFILENAME 30  /* filename consists of the mcast group inet_addr and port*/
/* List for inside clients wanting same multicast address/port */
static struct ic {
	unsigned long addr;
	unsigned short port;
	char user[MAXUSRSTR];
} other_inside_client[MAXHOSTS];
static int num_inside_clients = 0;
static char client_list_file[MAXFILENAME];
static char lock_client_file[MAXFILENAME];

/* File to use for locking when getting ports */
#define PORT_LOCK_FILE "/tmp/port_lock"

#define MAXMSG		80*1024 
#define SMMAXMSG	32*1024

#ifndef MAX
#define MAX(x,y)	((x) > (y) ? (x) : (y))
#endif /* MAX */


#define IS_MULTICAST(i)	(((long)(i) & 0x000000f0) == 0x000000e0)

#define TRUE 1
#define FALSE 0


main(ac,av)
int     ac;
char    *av[];
{       
	int		x;
	int		nfds;
	fd_set		fdset;
	static  time_t	ontime;
	static  time_t	offtime;
	int		port;
	struct timeval  timo;
	struct in_addr	mg;


	/* 
	 * All messages in log will have "mbone-gw" and the PID of 
	 * the MBone proxy process.
	 */ 
#ifndef LOG_DAEMON
	openlog("mbone-gw",LOG_PID);
#else
	openlog("mbone-gw",LOG_PID|LOG_NDELAY,LFAC);
#endif

#ifndef FWTK1_3
	/* Check if -daemon option specified */
        if (ac > 1 && !strcmp(av[1], "-daemon")) {
		if (ac > 2) {
			port = str_to_port(av[2]);
			ac -= 2;
			av += 2;
		}
		else {
			port = str_to_port("mbonesrv");
			ac--;
			av++;
		}
		syslog(LLEV,"Starting daemon mode on port %d",port);
		do_daemon(port);
	}
#endif

        time(&ontime);  

	/* Open the netperm table and get the mbone-gw rules */
	if((confp = cfg_read("mbone-gw")) == (Cfg *)-1)
		exit(1);

	authuser[0] = '\0';


/* BINDDEBUG is defined in the Makefile.  It is used to debug the proxy.  
   The wrapper must also be built with BINDDEBUG.  */
#ifdef BINDDEBUG
	/* Get a socket for control connections with an inside client */
	if (get_tcp_ctrl_sock() < 0) {
		syslog(LLEV,"get_tcp_ctrl_sock failed: %m");
		exit(1);
	}
#endif

	/* Get and check the client's address */
	if(peername(0,rladdr,riaddr,sizeof(riaddr))) {
		syslog(LLEV,"fwtksyserr: cannot get peer name");
		exit(1);
	}

	inside_host = inet_addr(riaddr);

	/* Read the Multicast information from client */
	if (get_minfo() < 0) {
		syslog(LLEV,"get_minfo failed: %m");
		exit(1);
	}

	/* Check the netperm-table entries for mbone-gw */
	/* If authentication needed, authenticate */
	if (cfg_check() < 0) {
		syslog(LLEV,"authentication failed");
		exit(1);
	}

	/* Check if Multicast group a valid destination */
	mg.s_addr = Mcast_group;
#ifndef FWTK1_3
	if (dest_check(inet_ntoa(mg)) < 0) {
		exit(1);
	}
#endif

	/* Reply to the client with Unicast port */
	if (reply_minfo() < 0) {
		syslog(LLEV, "reply_minfo error");
		exit(1);
	}

	/* Setup Multicast sockets */
	if (setup_mcast_sockets(&mcast_recv_sock, 
			  &mcast_send_sock,
			  Mcast_port) < 0) {
		syslog(LLEV,"setup_mcast_sockets failed: %m");
		exit(1);
	}
	/* If RTCP used, setup control protocol sockets */
	if (Mcast_rtp) {
		if (setup_mcast_sockets(&mcast_ctrl_recv_sock,
				  &mcast_ctrl_send_sock, 
				  Mcast_port+1) < 0) {
			syslog(LLEV,"setup_(ctrl)_sockets failed: %m");
			exit(1);
		}
	}

	/* Join the Multicast Group on outside interface */
	/* Set multicast socket options */ 
	if (set_moptions() < 0)
		exit(1);
	syslog(LLEV, "Appl: %s Group: %s port: %d ttl: %d", Mcast_appl, 
		inet_ntoa(mg), Mcast_port, Mcast_ttl, Mcast_appl);

	/* Establish signal for reading inside client file */
	signal(SIGALRM, sig_alarm);

	/* Create the names of the inside client list file and lock file */
	sprintf(client_list_file, "/tmp/%x-%x", (int)Mcast_group, Mcast_port);
	sprintf(lock_client_file, "/tmp/%x-%x-lock", (int)Mcast_group, Mcast_port);

	/* Read number of other inside clients */
	read_inside_client_list(TRUE);

	/* add client to list. Bind ic_sock */
	if (client_list_add(inside_host, Ucast_port, Mcast_user) < 0) {
		syslog(LLEV, "Error adding self to client list: %m");
	}

	/* get the number of fds for select() */
	nfds = MAX(mcast_recv_sock, client_recv_sock);
	if (Mcast_rtp) {
		nfds = MAX(nfds, mcast_ctrl_recv_sock);
		nfds = MAX(nfds, client_ctrl_recv_sock);
	}
	nfds++;

	/* Main loop */
	while(1) {
		/* Check if timelimit hit */
		if (leaveflag > 0) {
			syslog(LLEV,"Time limit reached");
			send_error("Time limit reached.");
			break;
		}

		FD_ZERO(&fdset);
		FD_SET(mcast_recv_sock, &fdset);
		FD_SET(client_recv_sock, &fdset);
		if (Mcast_rtp) {
			FD_SET(mcast_ctrl_recv_sock,&fdset);
			FD_SET(client_ctrl_recv_sock,&fdset);
		}
		FD_SET(0,&fdset);
		timo.tv_sec = timeout;
		timo.tv_usec = 0;

		/* Wait for something to arrive on the receive sockets */
		if((x = select(nfds,&fdset,(fd_set *)0,(fd_set *)0,&timo))<0){
			/* ignore interrupt */
			if (errno == EINTR) continue;
			syslog(LLEV,"select error: %m");
			send_error("Error occurred.");
			break;
		}

		if (x == 0) { 	/* Timeout reached */
			syslog(LLEV,"Timeout reached");
			send_error("Timeout reached.");
			break; 
		}

		/* Multicast datagram arrived? */
		if (FD_ISSET(mcast_recv_sock, &fdset)) {
			if (send_in(mcast_recv_sock, client_send_sock, 
						client_sin) < 0) {
				syslog(LLEV,"send_in error");
				send_error("Error occurred.");
				break;
			}
		}

		/* Unicast datagram arrived? */
		if (FD_ISSET(client_recv_sock, &fdset)) {
			if (send_out(client_recv_sock, 
				     mcast_send_sock) < 0) {
				syslog(LLEV,"send_out error");
				send_error("Error occurred.");
				break;
			}
		}	
		if (Mcast_rtp) {
			/* Multicast ctrl datagram arrived? */
			if (FD_ISSET(mcast_ctrl_recv_sock, &fdset)) {
				if (send_in(mcast_ctrl_recv_sock, 
					    client_ctrl_send_sock,
					    client_ctrl_sin) < 0) {
					syslog(LLEV,"send_in (ctrl) error");
					send_error("Error occurred.");
					break;
				}
			}

			/* Unicast ctrl datagram arrived? */
			if (FD_ISSET(client_ctrl_recv_sock, &fdset)) {
				if (send_out(client_ctrl_recv_sock,
					     mcast_ctrl_send_sock) < 0) {
					syslog(LLEV,"send_out (ctrl) error");
					send_error("Error occurred.");
					break;
				}
			}
		}

		/* Drop Membership TCP packet arrived? */
		if (FD_ISSET(0, &fdset)) {
			read_drop_minfo();
			break;
		}
	}

	/* Remove client from inside client list */
	client_list_remove(inside_host, Mcast_user);

	/* If sdr, drop the sdr group membership on the inside interface */
	if (!strncmp(Mcast_appl, "sdr", 3)) {
		drop_membership(client_recv_sock, Mcast_group, 
			INSIDE_INTERFACE);
	}

	/* Drop multicast group membership on outside interface */
	drop_membership(mcast_recv_sock, Mcast_group, OUTSIDE_INTERFACE);

	/* Log the exit */
	time(&offtime);
	syslog(LLEV,"exit user=%s appl=%s client= %s duration=%d", 
	    Mcast_user, Mcast_appl, rladdr, offtime-ontime);

	return(0);
}


/* 
 * cfg_check()
 * Get the information specified for mbone-gw in the configuration 
 * file (netperm-table) and process it.
 * If authentication is specified, getauth() called.
 */ 
static int
cfg_check()
{

	Cfg             *cf;

	int             runuid = -1;
	int             rungid = -1;
	char            xuf[1024];

	if((cf = cfg_get("groupid",confp)) != (Cfg *)0) {
		if(cf->argc != 1) {
			syslog(LLEV,"fwtkcfgerr: groupid must have one parameter, line %d",cf->ln);
			return(-1);
		}
		if((rungid = mapgid(cf->argv[0])) == -1) {
			syslog(LLEV,"fwtkcfgerr: cannot map %s to gid",cf->argv[0]);
			return(-1);
		}
	}

	if((cf = cfg_get("userid",confp)) != (Cfg *)0) {
		if(cf->argc != 1) {
			syslog(LLEV,"fwtkcfgerr: userid must have one parameter, line %d",cf->ln);
		return(-1);
		}
		if((runuid = mapuid(cf->argv[0])) == -1) {
			syslog(LLEV,"fwtkcfgerr: cannot map %s to uid",cf->argv[0]);
			return(-1);
		}
	}
	if((cf = cfg_get("directory",confp)) != (Cfg *)0) {
		if(cf->argc != 1) {
			syslog(LLEV,"fwtkcfgerr: chroot must have one parameter, line %d",cf->ln);
			return(-1);
		}
		chdir("/");
		if(chdir(cf->argv[0])) {
			syslog(LLEV,"fwtksyserr: chdir %s: %m",cf->argv[0]);
			return(-1);
		}

		if(chroot(cf->argv[0])) {
			syslog(LLEV,"fwtksyserr: chroot %s: %m",cf->argv[0]);
			return(-1);
		}
		chdir("/");
	}
	if(rungid != -1 && setgid(rungid)) {
		syslog(LLEV,"fwtksyserr: cannot setgid %d: %m",rungid);
		return(-1);
	}
	if(runuid != -1 && setuid(runuid)) {
		syslog(LLEV,"fwtksyserr: cannot setuid %d: %m",runuid);
		return(-1);
	}

        /* see if this is someone we are even willing to converse with */
	if(!oktotalkto()) {
		if((cf = cfg_get("denial-msg",confp)) != (Cfg *)0) {
			if(cf->argc != 1) {
				syslog(LLEV,"fwtkcfgerr: denial-msg must have one parameter, line %d",cf->ln);
				return(-1);
			}
			if(sayfile(0,cf->argv[0])) {
				syslog(LLEV,"fwtksyserr: cannot display denial-msg %s: %m",cf->argv[0]);
				return(-1);
			}
			return(0);
		} else {
			sprintf(xuf,"%s/%s not authorized to use mbone proxy",rladdr,riaddr);
			say(0,xuf);
			return(-1);
		}
	}

        if((cf = cfg_get("timeout",confp)) != (Cfg *)0) {
		if(cf->argc != 1) {
			syslog(LLEV,"fwtkcfgerr: timeout must have one parameter, line %d",cf->ln);
			exit(1);
		}
		timeout = atoi(cf->argv[0]);
		if(timeout <= 0) {
			syslog(LLEV,"fwtkcfgerr: timeout %s invalid, line %d",cf->argv[0],cf->ln);
			exit(1);
		}
	}

        if((cf = cfg_get("timelimit",confp)) != (Cfg *)0) {
		if(cf->argc != 1) {
			syslog(LLEV,"fwtkcfgerr: timelimit must have one parameter, line %d",cf->ln);
			exit(1);
		}
		timelimit = atoi(cf->argv[0]);
		if(timelimit <= 0) {
			syslog(LLEV,"fwtkcfgerr: timelimit %s invalid, line %d",cf->argv[0],cf->ln);
			exit(1);
		}
	}

	if (authsdrneeded && (sdr_running() > 0))
		return(0);

	if ((authneeded || authsdrneeded) && getauth()) {
		syslog(LLEV,"exit host=%s/%s no auth",rladdr,riaddr);
		exit(1);
	}

	return(0);
}

#ifndef FWTK1_3
/* 
 * dest_check()
 * If -dest specified, checks to make sure multicast group is ok.
 */
int
dest_check(dest)
char *dest;
{
	char    **xp;
	int     x;

        if(validests != (char **)0) {
		for(xp = validests; *xp != (char *)0; xp++) {
			if(**xp == '!' && mcast_hostmatch(*xp + 1,dest)) {
				return(baddest(0,dest));
			} else {
				if(mcast_hostmatch(*xp,dest))
					break;
			}
		}
		if(*xp == (char *)0)
			return(baddest(0,dest));
	}
	return(0);
}
#endif


/*
 * baddest()
 * Originally from ftp-gw.c.  Sends the bad destination message to the user.
 */
baddest(fd,dest)
int     fd;
char    *dest;
{
        Cfg     *cf;
	char    buf[BUFSIZ];

	syslog(LLEV,"deny host=%s/%s connect to %.256s",rladdr,riaddr,dest);
	if((cf = cfg_get("denydest-msg",confp)) != (Cfg *)0) {
		if(cf->argc != 1) {
			syslog(LLEV,"fwtkcfgerr: denydest-msg must have one parameter, line %d",cf->ln);
			return(1);
		}
		if(sayfile(fd,cf->argv[0])) {
			syslog(LLEV,"fwtksyserr: cannot display denydest-msg %s: %m",cf->argv[0]);
			return(1);
		}
		return(0);
	}
	sprintf(buf,"Not permitted to connect to %s",dest);
	return(say(fd,buf));
}


/* 
 * Check to see if an sdr is running on the same client host and same user.
 */
int
sdr_running()
{

	char sdr_list_file[MAXFILENAME];
	char lock_sdr_file[MAXFILENAME];
	struct ic sdr_client;
	int fd, sdrlockfd;
	int found = 0;
	int i;


	/* Get the names of the sdr inside client list and lock file */
	sprintf(sdr_list_file, "/tmp/%x-%x", inet_addr(SDR_ADDR), 
			atoi(SDR_PORT));
	sprintf(lock_sdr_file, "/tmp/%x-%x-lock", inet_addr(SDR_ADDR), 
			atoi(SDR_PORT));

	/* Open the lock file */
	if ((sdrlockfd = open(lock_sdr_file, O_RDONLY, 0400)) < 0) {
		return(0);
	}

	/* Lock the lock file */
	lock_fd(sdrlockfd);

	/* Open the sdr inside client list */
	if ((fd = open(sdr_list_file, O_RDONLY, 0400)) < 0) {
		lockun_fd(sdrlockfd);
		close(sdrlockfd);
		return(0);
	}

	for (i=1; i<=MAXHOSTS; i++) { 
		bzero((char *)&sdr_client, sizeof(sdr_client)); 
		if (read(fd, &sdr_client, sizeof(struct ic)) <=0) 
			break;
		if (sdr_client.addr == inside_host){
			found = i;
			break;
		}
	}

	close(fd);
	lockun_fd(sdrlockfd);
	close(sdrlockfd);
	return(found);
}


#ifndef FWTK1_3
/* Modified hostmatch because we cannot DNS lookup multicast addresses */
/* Used when checking Multicast group address in validests list */
mcast_hostmatch(pattern,name)
char	*pattern;
char	*name;
{
	struct	hostent		*hp;
	struct	sockaddr_in	sin;
	char			pat[512];
	char			nam[512];
	char			*p;
	int			x;
	int			y;

	/* copy into private area because we lowercase names */
	if((x = strlen(pattern)) >= sizeof(pat)) {
		syslog(LLEV,"fwtkcfgerr: pattern %s too long!",pattern);
		return(0);
	}
	strcpy(pat,pattern);
	for(y = 0; y < x; y++)
		if(isupper(pat[y]))
			pat[y] = tolower(pat[y]);


	if((x = strlen(name)) >= sizeof(nam)) {
		syslog(LLEV,"fwtkcfgerr: name %s too long!",name);
		return(0);
	}
	strcpy(nam,name);
	for(y = 0; y < x; y++)
		if(isupper(nam[y]))
			nam[y] = tolower(nam[y]);


	/* is the pattern numeric ? ,pjc: num:mask? */
	p = pat;
	while(*p != '\0' &&
	    (*p == '.' || *p == '*' || *p == '?' || isdigit(*p) || *p == ':'))
		p++;


	    /* match against a text name */
	    if(*p != '\0') {
		long		f;
		char		*p = nam;
		struct in_addr	*hp_addr;
		int		eq = 0;

		while(*p != '\0' && (*p == '.' || isdigit(*p)))
			p++;

		/* if the name is also a text name, just match */
		if(*p != 0)
			return(namatch(pat,nam));

		/* fooey, it's not, we need to reverse lookup */
		if((f = inet_addr(nam)) == (long) -1) {
			syslog(LLEV,"fwtkcfgerr: inet_addr, malformed address: %s",nam);
			return(0);
		}

		/* If Multicast address, don't do the DNS reverse lookup */
		if (!IS_MULTICAST(f)) {
		    hp = gethostbyaddr((char *)&f,sizeof(f),AF_INET);
		    if(hp == (struct hostent *)0)
			return(namatch(pat,"unknown"));

		    /* rewrite name */
		    if((x = strlen(hp->h_name)) >= sizeof(nam)) {
			syslog(LLEV,"fwtksyserr: name %s too long!",hp->h_name);
			return(0);
		    }
		    strcpy(nam,hp->h_name);
		    for(y = 0; y < x; y++)
			if(isupper(nam[y]))
				nam[y] = tolower(nam[y]);

		    /* cross-check reverse lookup to try to detect DNS spoofs */
		    hp = gethostbyname(nam);
		    if(hp == (struct hostent *)0)
			return(namatch(pat,"unknown"));

		    while((hp_addr = (struct in_addr *)*hp->h_addr_list++) != (struct in_addr *)0) {
			if(bcmp(hp_addr,&f,hp->h_length) == 0) {
				eq = 1;
				break;
			}
		    }

		    if(!eq) {
			syslog(LLEV,"securityalert: possible spoof - DNS lookup for host %s gives address %s but the reverse lookup is %s",
				nam,inet_ntoa(f),hp->h_name);
			return(namatch(pat,"unknown"));
		    }
		}

		/* name valid: match */
		return(namatch(pat,nam));
	}



	/* match against a numeric pattern */
	/* pjc: or num:mask */

	p = nam;
	while(*p != '\0' && (*p == '.' || isdigit(*p)))
		p++;

	/* all numeric match is easy ! */
	if(*p == '\0')
		return(maskmatch(pat,nam));

	/* get address and covert to numbers to match on */
	hp = gethostbyname(nam);

	/* unknown host can never match numeric spec */
	if(hp == (struct hostent *)0)
		return(0);

	/* match names */
	bcopy(hp->h_addr,&sin.sin_addr,hp->h_length);
	return(maskmatch(pat,inet_ntoa(sin.sin_addr)));
}
#endif

/* Setup addresses and sockets */
static int
setup_mcast_sockets(mrsock, mssock, mport)
int *mrsock, *mssock;	   /* Mcast Recv/Send sockets */
unsigned short mport; /* multicast port */

{

	struct sockaddr_in sin;
	int on = 1;

	/* Multicast Receive socket */
	/* Receive from anyone.  Set sock option to reuse addr/port */
	if (bind_udp_sock(mrsock, Mcast_group, mport, 0, 0, 1) < 0) { 
		syslog(LLEV,"bind_udp_sock mrsock error: %m");
		return(-1);
	}

	/* Multicast Send socket */
	/* Local address is Outside ethernet interface */
	/* Foreign address is Multicast group address/port */
	if (bind_udp_sock(mssock, 
			inet_addr(OUTSIDE_INTERFACE), 0,
			Mcast_group, mport, 0) < 0) {
		syslog(LLEV,"bind_upd_sock mssock error: %m");
		return(-1);
	}

	/* If not sdr, increase buffer size */
	if (strncmp(Mcast_appl, "sdr", 3)) {
		/* An attempt to write a datagram larger than the 
		 * default returns an error, so increase send and 
		 * receive buffer sizes. */ 
		if (set_bufsize(*mrsock, SO_RCVBUF)) {
			syslog(LLEV,"set_bufsize mrsock error: %m");
			return(-1);
		}
		if (set_bufsize(*mssock, SO_SNDBUF)) {
			syslog(LLEV,"set_bufsize mssock error: %m");
			return(-1);
		}
	}
	return(0);
}


/*
 * Add multicast group membership, disable loopback, and set ttl 
 * value on appropriate sockets.
 */
static int
set_moptions()
{

	unsigned char loop;

	/* Join the multicast group to receive that group's datagrams */
	/*  from the outside network interface */
	if (add_membership(mcast_recv_sock, 
			Mcast_group, 
			OUTSIDE_INTERFACE) < 0)
		return(-1);

	/* Disable loopback when sending out */
	if (disable_loopback(mcast_send_sock) < 0)
		return(-1);


	/* Set TTL value for sending multicast datagrams */
	if (set_ttl(Mcast_ttl) < 0) 
		return(-1);

	return(0);
}


/* 
 * Add multicast group membership on specified interface.
 */
static int
add_membership(sock, group, iface)
int sock;
unsigned long group; 	/* Multicast group address */
char *iface;		/* interface */
{

/* Some operating systems don't use IP_ADD_MEMBERSHIP, and so don't define it
   in /usr/include/netinet/in.h.  Do not define it yourself.  */
#ifdef IP_ADD_MEMBERSHIP
	struct in_addr mg;
	struct 	ip_mreq mreq;		/* Multicast {interface, group} */
	bzero((char *)&mreq, sizeof(mreq));
	mreq.imr_multiaddr.s_addr = group;  /* group addr */
	mreq.imr_interface.s_addr = inet_addr(iface);		
	mg.s_addr = group;
	if (setsockopt(sock, 
			IPPROTO_IP, 
			IP_ADD_MEMBERSHIP, 
			(char *)&mreq, 
			sizeof(mreq)) < 0) {
		syslog(LLEV,"Error adding membership %s on interface %s: %m",
			inet_ntoa(mg), OUTSIDE_INTERFACE);
		return(-1);
	}

	syslog(LLEV, "ADDED membership %s on interface %s ",
		    inet_ntoa(mg), iface);
#endif
	return(0);
}


/* 
 * Drop multicast group membership on specified interface.
 */
static void
drop_membership(sock, group, iface)
int sock;
unsigned long group; 	/* Multicast group address */
char *iface;		/* interface */
{

/* Some operating systems don't use IP_DROP_MEMBERSHIP, and so don't define it
   in /usr/include/netinet/in.h.  Do not define it yourself.  */
#ifdef IP_DROP_MEMBERSHIP
	struct in_addr mg;
	struct 	ip_mreq mreq;		/* Multicast {interface, group} */
	bzero((char *)&mreq, sizeof(mreq));
	mreq.imr_multiaddr.s_addr = group;  /* group addr */
	mreq.imr_interface.s_addr = inet_addr(iface);		
	mg.s_addr = group;
	if (setsockopt(sock, 
			IPPROTO_IP, 
			IP_DROP_MEMBERSHIP, 
			(char *)&mreq, 
			sizeof(mreq)) < 0) {
		syslog(LLEV,"Error dropping membership %s on interface %s: %m",
				inet_ntoa(mg), iface);
		return;
	}
	syslog(LLEV, "DROPPED membership %s on interface %s", 
				inet_ntoa(mg), iface);

#endif
	return;
}


/* Disable hearing of multicast sends from self */
/* If hw receives its own transmissions, this will not stop loopback */
static int
disable_loopback(sock)
int sock;
{
	char loop = 0;
	if (setsockopt(sock, 
			IPPROTO_IP, 
			IP_MULTICAST_LOOP, 
			&loop, 
			sizeof(loop)) < 0) {
		syslog(LLEV,"Error disabling multicast loopback: %m");
		return(-1);
	}
	return(0);
}


/*
 * Set the Time to live (ttl) value on outgoing multicast datagrams.
 */
int
set_ttl(ttl)
unsigned char ttl;
{
	/* Set TTL value for sending multicast datagrams */
	if (setsockopt(mcast_send_sock, 
			IPPROTO_IP, 
			IP_MULTICAST_TTL,
			(char *)&ttl, 
			sizeof(ttl)) < 0) {
		syslog(LLEV,"Error IP_MULTICAST_TTL(%d): %m", ttl);
		return(-1);
	}
	return 0;
}


/*
 * Gets a socket, and binds and (optionally) connects the local and 
 * foreign addresses and ports.
 */
static int
bind_udp_sock(sock, l_addr, l_port, f_addr, f_port, reuse)
int	*sock;
unsigned long l_addr, f_addr; 
unsigned short f_port, l_port;	
int reuse; 	/* 1 indicates if set sockopt to reuse port*/
{

	struct sockaddr_in sin;
	int on = 1;

	/* Allocate a UDP socket */
	if ((*sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		syslog(LLEV,"bind_udp_sock: socket error: %m");
		return(-1);
	}

	if (reuse) {
		/* Allow others to bind to addr/port */
		if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
				sizeof(on)) < 0) {
			syslog(LLEV,"setsockopt SO_REUSEADDR error: %m");
			return(-1);
		}
/* Some operating systems don't have the socket option SO_REUSEPORT, and so 
   don't define it.  Do not define it yourself.  */
#ifdef SO_REUSEPORT
		if (setsockopt(*sock, SOL_SOCKET, SO_REUSEPORT, (char *)&on,
				sizeof(on)) < 0) {
			syslog(LLEV,"setsockopt SO_REUSEPORT error: %m");
			return(-1);
		}
#endif
	}

	/* Fill in the local address information */
	bzero((char *)&sin, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = l_addr;
	sin.sin_port = htons(l_port);

	/* Bind the address */
	if (bind(*sock, (struct sockaddr *)&sin, sizeof(sin)) < 0){
		syslog(LLEV,"bind_udp_sock: bind error: %m");
		return(-1);
	}

	if (f_addr != 0) {
		/* Fill in the foreign address information */
		bzero((char *)&sin, sizeof(sin));
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = f_addr;
		sin.sin_port = htons(f_port);

		/* Complete the association */
		if (connect(*sock, (struct sockaddr *)&sin, sizeof(sin)) < 0){
			syslog(LLEV,"bind_udp_sock: connect error");
			return(-1);
		}
	}

	return(0);
}

/* 
 * Increase the default buffer size for the receive (SO_RCVBUF) or 
 * send (SO_SNDBUF) buffers.
 *
 */
static int
set_bufsize(sock, optname)
int sock;
int optname;  /* SO_RCVBUF or SO_SNDBUF */
{
	int bufsize = MAXMSG;
	if (setsockopt(sock, SOL_SOCKET, optname, (char *)&bufsize,
		sizeof(bufsize)) < 0) {
		bufsize = SMMAXMSG;
		if (setsockopt(sock, SOL_SOCKET, optname, (char *)&bufsize,
			sizeof(bufsize)) < 0) {
			return(-1);
		}
	}
	return(0);
}

/*
 * Read the information from the client which includes the multicast 
 * group address, port, and ttl of the MBone application.
 */
static int
get_minfo()
{
	struct	mcast_info minfo;	/* Multicast group enable info */
	int	n;			/* number of bytes from/to client */


	/* Read the packet with the multicast address information */
	n = read(tcpfd, (char *)&minfo, sizeof(minfo));
	if (n < sizeof(minfo)) {
		syslog(LLEV,"read mcast address info error");
		return(-1);
	}

	if (minfo.type != ntohs(MCAST_ADD)) {
		syslog(LLEV,"Did not recv expected MCAST_ADD from client");
		return(-1);
	}

	Mcast_group = minfo.mcast_group;
	Mcast_port = ntohs(minfo.mcast_port);
	Mcast_ttl = ntohs(minfo.ttl);
	Mcast_rtp = ntohs(minfo.rtp);
	bzero((char *)Mcast_user, sizeof(Mcast_user));
	strncpy(Mcast_user, minfo.mcast_user, MAXUSRSTR-1);
	bzero((char *)Mcast_appl, sizeof(Mcast_appl));
	strncpy(Mcast_appl, minfo.mcast_appl, MAXAPPLSTR-1);

	/* If application uses RTPv2, then an even port needed for data */
	if (Mcast_rtp == 2) {
		Mcast_port &= ~1; /* make sure it's even number */
			/* even is data port, odd is ctrl port */
	}

	return(0);
}


/*
 * Reply to the client that the mcast_info was received successfully (or not).
 * Also set the client socket information with the client address and unicast 
 * port in client_sin.
 */
static int
reply_minfo()
{
	int	n;			/* number of bytes from/to client */
	struct mcast_reply fw_reply;
	int ret;

	bzero((char *)&fw_reply, sizeof(fw_reply));

	/* Get UDP port for client to (unicast) send data to fw */
	ret = get_ucast_port();
	if (ret < 0) {
		strncpy(fw_reply.reply, BAD_REPLY, sizeof(BAD_REPLY));
		syslog(LLEV, "get_ucast_port error");
	}
	else {
		strncpy(fw_reply.reply, GOOD_REPLY, sizeof(GOOD_REPLY));
		fw_reply.fw_ucast_port = htons(Ucast_port);
	}

	/* Reply to the client */
	n = write(tcpfd, (char *)&fw_reply, sizeof(fw_reply));
	if (n < sizeof(fw_reply)) {
		syslog(LLEV,"reply write error");
		return(-1);
	}
	if (ret < 0) 
		return(-1);

	/* Set up the UDP client send sin info */
	bzero((char *)&client_sin, sizeof(client_sin));
	client_sin.sin_family = AF_INET;
	client_sin.sin_addr.s_addr = inside_host;
	client_sin.sin_port = htons(Ucast_port);

	if (Mcast_rtp) {
		bzero((char *)&client_ctrl_sin, sizeof(client_ctrl_sin));
		client_ctrl_sin.sin_family = AF_INET;
		client_ctrl_sin.sin_addr.s_addr = inside_host;
		client_ctrl_sin.sin_port = htons(Ucast_port+1);
	}

	return(0);
}


/* 
 * Get UDP port(s) to receive from the inside client.
 * This port number gets sent to the client to be used by both to 
 * receive datagrams.  We can complete the association 
 * {protocol, local-addr, local-proc, foreign-addr, foreign-proc}
 * of client_send_sock.  Client_recv_sock's association will be completed
 * when the first datagram arrives, then the foreign port/proc will be known.
*/
static int
get_ucast_port()
{
	struct sockaddr_in sin;
	int sinlen;
	int lockfd;

	sinlen = sizeof(sin);

	if ((lockfd = port_lock()) < 0) {
		syslog(LLEV, "Error getting lock on port_lock file: %m");
		/* continue */
	}


	/* If sdr, then use Mcast_port, and setsock option for reuse port */
	if (!strncmp(Mcast_appl, "sdr", 3)) {
		Ucast_port = Mcast_port;
		if (bind_udp_sock(&client_recv_sock, 
				Mcast_group, Ucast_port,
				0, 0, 1) < 0) {
			port_unlock(lockfd);
			syslog(LLEV,"client_recv_sock(sdr) error: %m");
			return(-1);
		}
	}
	else {
		/* Get an ephemeral port to be used as the Ucast_port.  */
		if (bind_udp_sock(&client_recv_sock, 
				INADDR_ANY, Ucast_port,
				0, 0, 0) < 0) {
			port_unlock(lockfd);
			syslog(LLEV,"client_recv_sock error: %m");
			return(-1);
		}
	}

	/* If sdr, join sdr group on inside i/f */
	if (!strncmp(Mcast_appl, "sdr", 3)) {
		if (add_membership(client_recv_sock, 
				Mcast_group, 
				INSIDE_INTERFACE) < 0) {
			port_unlock(lockfd);
			return(-1);
		}
	}

	/* Get the port number */
	bzero((char *)&sin, sinlen);
	if (getsockname(client_recv_sock, (struct sockaddr *)&sin, 
			&sinlen) < 0) {
		port_unlock(lockfd);
		syslog(LLEV, "getsockname error");
		return(-1);
	}

	Ucast_port = ntohs(sin.sin_port);


	/* RTP uses two consecutive ports.  One for data, other for control 
	 * RTPv2 specifies the data port to be greatest even integer less 
	 * than or equal to specified port, ctrl port is one greater than 
         *  data port.
	 * RTPv1 doesn't specify even/odd for data/ctrl
	 */
	/* Vic uses RTPv2 */
	/* Vat (currently) uses RTPv1 */
	if (Mcast_rtp == 2) {
		Ucast_port &= ~1; /* Make sure even port number */
		/* Check if it was even. If not, try again */
		if (ntohs(sin.sin_port) != Ucast_port) {
			close(client_recv_sock);
			Ucast_port += 2; /* get next even port */
			if (bind_udp_sock(&client_recv_sock, 
					INADDR_ANY, 
					Ucast_port,
					0, 0, 0) < 0) {
				port_unlock(lockfd);
				syslog(LLEV,"client_recv_sock error: %m");
				return(-1);
			}
		}
	}

	if (Mcast_rtp) {	/* rtpv1 and rtpv2 */
		/* now check Ucast_port+1 */
		if (bind_udp_sock(&client_ctrl_recv_sock, 
				INADDR_ANY, Ucast_port+1,
				0, 0, 0) < 0) {
			port_unlock(lockfd);
			syslog(LLEV,"client_ctrl_recv_sock error: %m");
			return(-1);
		}
	}


	/* Get client_send_sock */
	if (bind_udp_sock(&client_send_sock, 
			INADDR_ANY, 0,
			0, 0, 0) < 0) {
		port_unlock(lockfd);
		syslog(LLEV,"client_send_sock error: %m");
		return(-1);
	}
	if (Mcast_rtp) {
		/* Get client_ctrl_send_sock */
		if (bind_udp_sock(&client_ctrl_send_sock, 
				INADDR_ANY, 0,
				0, 0, 0) < 0) {
			port_unlock(lockfd);
			syslog(LLEV,"client_ctrl_send_sock error: %m");
			return(-1);
		}
	}

	port_unlock(lockfd);
	return(0);
}


/*
 * There is a problem when multiple applications are started at one time.
 * Getting two consecutive ports needs to be done while no other application
 * is getting a port, or a port may be taken by another application and a 
 * bind error will occur.
 * This lock will block multiple applications from getting ports at the 
 * same time.
 * Another process (not mbone-gw process) could, however, get the next 
 * consecutive port.
 */
static int
port_lock()
{
	int fd;
	fd = open(PORT_LOCK_FILE, O_CREAT|O_EXCL|O_RDONLY, 0600);
	if (fd < 0) { /* file exists */
		fd = open(PORT_LOCK_FILE, O_RDONLY, 0400);
		if (fd < 0) { /* error opening file */
			syslog(LLEV, "error opening port_lock file: %m");
			return(-1);
		}
	}
	lock_fd(fd);
	return(fd);
}

/* Unlock routine */
static void
port_unlock(fd)
int fd;
{
	if (fd >=0) {
		lockun_fd(fd);
		close(fd);
	}
	return;
}


/*
 * When reading/writing the inside client list file, there could be problems
 * when multiple proxies open the same file.  This lock will be used before
 * even opening the file to make sure only one proxy has it open at any time.
 */
static int
lock_client_list_file()
{
	int fd;
	fd = open(lock_client_file, O_CREAT|O_EXCL|O_RDONLY, 0600);
	if (fd < 0) { /* file exists */
		fd = open(lock_client_file, O_RDONLY, 0400);
		if (fd < 0) { /* error opening file */
			syslog(LLEV,"error opening %s: %m", lock_client_file);
			return(-1);
		}
	}
	lock_fd(fd);
	return(fd);
}
/* The unlock routine */
static void
unlock_client_list_file(fd)
int fd;
{
	lockun_fd(fd);
	close(fd);
	return;
}


/*
 * If anything is read from the client after initialization has occurred 
 * (i.e. the proxy is in the infinite select loop), it is probably a drop 
 * membership packet.  If anything is read from the client while in the 
 * infinite select loop, the proxy terminates.
 */
static void 
read_drop_minfo()
{
	struct	mcast_info minfo;	/* Multicast group enable info */
	int	n;			/* number of bytes from/to client */

	/* Read the packet with the MCAST_DROP type */
	n = read(tcpfd, (char *)&minfo, sizeof(minfo));
	if (n < sizeof(minfo)) {
		syslog(LLEV,"Error reading from client.");
		return;
	}

	else if (ntohs(minfo.type) != MCAST_DROP) {
		syslog(LLEV,"Did not recv expected MCAST_DROP from client");
	}

	return;
}


/*
 * Send an error message to the client.
 */
static void
send_error(msg)
char *msg;
{
	int	n;			/* number of bytes to client */
	char	reply[REPLY_SIZE];	/* reply to client */

	sprintf(reply, "%s %s", msg, ERROR_REPLY);
	n = write(tcpfd, (char *)reply, sizeof(reply));
	if (n < sizeof(reply)) {
		syslog(LLEV,"send_error() write error");
	}

	return;
}


/*
 * When something comes in from the client, the message is read and 
 * multicast outside.  Also if there are other inside clients which 
 * have joined the same conference (address and group), unicast the 
 * message to them.
 */
static int
send_out(cli_sock, mcast_sock)
int cli_sock, mcast_sock;
{
	static int firsttime = 1;
	char	msg[MAXMSG];
	int	n;
	int	sinlen;
	struct ic *host;
	struct sockaddr_in ic_sin;
	int i;

        struct sockaddr_in recv_sin;

	sinlen = sizeof(recv_sin);
	bzero((char *)&recv_sin, sinlen);
	n = recvfrom(cli_sock, (char *)msg, MAXMSG, 0,
			(struct sockaddr *)&recv_sin, &sinlen);
	if (n < 0) {
		syslog(LLEV,"recvfrom client error: %m");
		return(-1);
	}

	if (recv_sin.sin_addr.s_addr != inside_host) {
		/* Ignore.  Not from correct host */
		/* This can happen with sdr proxies */
		return(0);
	}

	if (firsttime) {
		/* Complete the association */
		/* This will make sure we only get delivered to cli_sock, 
		   datagrams from the client */
		if (connect(cli_sock,
				(struct sockaddr *)&recv_sin,
				sinlen) < 0){
			syslog(LLEV,"connect error: %m");
			return(-1);
		}
		firsttime = 0;
	}

	/* If sdr, then need to get the ttl from the text payload */
	if (!strncmp(Mcast_appl, "sdr", 3)) {
		/* Get beyond the first 4 bytes 
			(Session Announcement Protocol) */
		if (set_ttl(get_sdr_pkt_ttl(&msg[4])) < 0) 
			return(-1);
	}

	if (send(mcast_sock, msg, n, 0) != n) {
		syslog(LLEV,"sendto mcast error: %m");
		return(-1);
	}

	/* Check to see if need to send to other inside clients */
	if (num_inside_clients > 1) {
		for (i = 0, host = other_inside_client; 
		     i < num_inside_clients; 
		     host++, i++) {
			if (host->addr == inside_host) continue;
			bzero((char *)&ic_sin, sizeof(ic_sin));
			ic_sin.sin_family = AF_INET;
			ic_sin.sin_addr.s_addr = host->addr;
			ic_sin.sin_port = htons(host->port);
			if (sendto(ic_sock, msg, n, 0, 
					(struct sockaddr *)&ic_sin, 
					sizeof(ic_sin)) != n) {
				syslog(LLEV,"sendto internal ucast error: %m");
				syslog(LLEV,"not sending to internal other hosts");
				return(0);
			}
		}
	}
	return(0);
}


/*
 * When something comes in from outside, the message is read and 
 * unicast inside to the client.
 */
static int
send_in(mcast_sock, cli_sock, cli_sin)
int mcast_sock, cli_sock;
struct sockaddr_in cli_sin;
{
	char	msg[MAXMSG];
	int	n;
	struct sockaddr_in recv_sin;
	int	sinlen;
	int	ret;

	/* Receive from anyone on outside */
	sinlen = sizeof(recv_sin);
	bzero((char *)&recv_sin, sinlen);
	n = recvfrom(mcast_sock, (char *)msg, MAXMSG, 0, 
			(struct sockaddr *)&recv_sin, &sinlen);
	if (n < 0) {
		syslog(LLEV,"recvfrom mcast error: %m");
		return(-1);
	}

	/* Check if this is a loopback from fw.  Ignore if is */
	if (recv_sin.sin_addr.s_addr == inet_addr(OUTSIDE_INTERFACE))
		return(0);

	/* 
	 * With sdr, because both interfaces are accepting sdr's multicast
	 * address, and mcast_recv_sock and client_recv_sock bind the same 
	 * multicast address and port as the local address, both 
	 * mcast_recv_sock and client_recv_sock will receive the sdr 
	 * multicast datagram.  So ignore this one.  With multicast, 
	 * the datagram is delivered to all matching sockets (not just the 
	 * best matched one).
	 *
	 * Also since another inside client's session information will be
	 * delivered to mcast_recv_sock, lets just check for if inside.
	 */

	/* Check if from inside */
	if ((ret = is_inside(recv_sin)) < 0)
		return(ret);
	else if (ret == TRUE)
		return(0);

	/* Send to inside client only */
	if (sendto(cli_sock, msg, n, 0, (struct sockaddr *)&cli_sin, 
			sizeof(cli_sin)) != n) {
		syslog(LLEV,"sendto client error: %m");
		return(-1);
	}
	return(0);
}


/* 
 * Checks to see if message came from an inside system.  Returns TRUE if yes.
 */
static int
is_inside(sin)
struct sockaddr_in sin;
{
	int sinlen;
	static int sock = -1;
	struct sockaddr_in conn_sin;

	/* Allocate a UDP socket */
	if (sock < 0) {
		if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
			syslog(LLEV,"is_inside: socket error: %m");
			return(-1);
		}
	}
	if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0){
		syslog(LLEV,"is_inside: connect error");
		return(-1);
	}

	sinlen = sizeof(conn_sin);
	bzero((char *)&conn_sin, sinlen);
	if (getsockname(sock, (struct sockaddr *)&conn_sin, &sinlen) < 0) {
		syslog(LLEV,"is_inside: getsockname error");
		return(-1);
	}

	if (conn_sin.sin_addr.s_addr == inet_addr(INSIDE_INTERFACE)) {
		return(TRUE);
	}

	return(FALSE);
}


/* get_sdr_pkt_ttl() is used only in proxy for sdr. */
/* Get the ttl value from the text payload of the session description packet */
/* The TTL value is in the connection data field, which starts with "c=" 
 * c=<network type> <address type> <connection address>
 * Conferences using an IP multicast connection address must have a TTL. 
 * The TTL is appended to the address using a slash as a separator.
 * Example: c=IN IP4 224.2.1.1/127
 * For multiple multicast groups, there is the notation: 
 *   <base multicast address>/<ttl>/<number of addresses>
 * So we check for an ending slash also.
 */
static int
get_sdr_pkt_ttl(text)
char *text;
{
	char *cdata;
	char net_type[10], addr_type[10], conn_addr[80]; 
	char *ttl_string, *end_slash;

	if ((cdata = strstr(text, "c=")) == NULL) {
		return 1;
	}
	cdata += 2;
	sscanf(cdata, "%s %s %s", net_type, addr_type, conn_addr);
	if ((ttl_string = strchr(conn_addr, '/')) == NULL) {
		return 1;
	}
	*ttl_string = '\0';
	ttl_string++;
	return(atoi(ttl_string));
}


/* From ftp-gw.c */
/* Checks if the client system is permitted to use mbone-gw. */
static int
oktotalkto()
{
        Cfg     *cf;
	int     x;

	cf = cfg_get("hosts",confp);
	while(cf != (Cfg *)0) {
		if(cf->argc < 1)
			goto skip;
		for(x = 0; x < cf->argc; x++) {
			if(cf->argv[x][0] == '-')
				break;
			if(hostmatch(cf->argv[x],riaddr)) {
				if(cf->flags & PERM_DENY) {
					syslog(LLEV,"deny host=%s/%s use of gateway",rladdr,riaddr);
					return(0);
				}
				syslog(LLEV,"permit host=%s/%s use of gateway",rladdr,riaddr);
				return(acceptrule(cf) == 0);
			}
		}
skip:
		cf = cfg_get("hosts",(Cfg*)0);
	}
	syslog(LLEV,"deny host=%s/%s use of gateway",rladdr,riaddr);
	return(0);
}


/* Originally from ftp-gw.c.  Modified slightly for mbone-gw. */
/* Checks the options on the netperm-table rule appropriate for the host */
static int
acceptrule(c)
Cfg *c;
{
        int     x;
	void    (*op)();

	for(x = 1; x < c->argc; x++) {
		/* skip nonoptions */
		if(c->argv[x][0] != '-')
			continue;

		/* options that take no parameters */
		/* turn on auth for all mbone applications */
		if(!strcmp(c->argv[x],"-authall") || 
		   !strcmp(c->argv[x],"-auth")) {
			authneeded = 1;
			continue;
		}

		/* turn on auth for only sdr */
		if(!strcmp(c->argv[x],"-authsdr")) {
			authsdrneeded = 1;
			continue;
		}

		/* options that take parameters and lists */
		op = 0;
		if(!strcmp(c->argv[x],"-dest"))
			op = accept_setdest;
		if(op == 0) {
			syslog(LLEV,"fwtkcfgerr: bad option line %d: %s",c->ln,c->argv[x]);
			return(1);
		}
		if(++x >= c->argc) {
			syslog(LLEV,"fwtkcfgerr: malformed line %d: missing option",c->ln);
			return(1);
		}
		if(!strcmp(c->argv[x],"{")) {
			while(1) {
				if(++x >= c->argc) {
				syslog(LLEV,"fwtkcfgerr: malformed line %d: missing option",c->ln);
				return(1);
				}
				if(!strcmp(c->argv[x],"}"))
					break;
				(*op)(c->argv[x],c->ln);
			}
		} else
			(*op)(c->argv[x],c->ln);
	}
	return(0);

}


/* From ftp-gw.c */
static  void
accept_setdest(v,c)
char    *v;
int     c;
{
        int     dests = 0;

	if(validests == (char **)0)
		validests = (char **)malloc(sizeof(char *) * 2);
	else {
		for(dests = 0; validests[dests] != (char *)0; dests++)
			;
		validests = (char **)realloc(validests,(dests + 2) * sizeof(char *));
	}
	if(validests == (char **)0)
		return;
	validests[dests] = v;
	validests[dests + 1] = (char *)0;
}


/* Originally from rlogin-gw.  Modified for mbone-gw. */
/* Performs the authentication */ 
static int
getauth()
{
        static char     prom[] = "Username: ";
	static char     pprom[] = "Password: ";
	static char     noauth[] = "Cannot connect to authentication server";
	static char     toobad[] = "Too many failed login attempts";
	static char     toobig[] = "User-ID too long";
	static char     lostit[] = "Lost connection to authentication server";
	static char     loggedin[] = "Login Accepted";
	char            buf[MAXMSG];
	char            cbuf[128];
	char            ubuf[128];
	int             bad = 5;
	int             x;

	/* open connection to auth server */
	if(auth_open(confp)) {
		(void)sayn(0,noauth,sizeof(noauth));
		return(1);
	}

	/* get welcome message from auth server */
	if(auth_recv(buf,sizeof(buf)))
		goto lostconn;
	if(strncmp(buf,"Authsrv ready",13)) {
		(void)say(0,buf);
		auth_close();
		return(1);
	}


	while(bad--) {
		/* prompt for user ID */
		if(write(tcpfd,prom,sizeof(prom)) != sizeof(prom))
			goto close_go;

		/* get user ID from user */
		bzero(buf, sizeof(buf));
		if(read(tcpfd,(char *)buf,sizeof(buf)) < 0)
			goto close_go;

                /* send user ID to auth server */
		if((x = strlen(buf)) == 0)
			continue;
		if(x > 24) {
			if(sayn(0,toobig,sizeof(toobig)))
				goto close_go;
			continue;
		}

		strcpy(ubuf,buf);
		if(strlen(rladdr) + strlen(riaddr) + strlen(buf) + 100 > 512)
			sprintf(cbuf,"authorize %s",buf);
		else
			sprintf(cbuf,"authorize %s 'mbone-gw %s/%s'",buf,rladdr,riaddr);
	syslog(LLEV, "USERNAME FROM CLIENT: %s", cbuf);
		if(auth_send(cbuf))
			goto lostconn;
		if(auth_recv(buf,sizeof(buf)))
			goto lostconn;
		if(!strncmp(buf,"challenge ",10)) {
			x = strlen(&buf[10]);
			x++; /* added one to send null to indicate end */
			if(write(tcpfd,&buf[10],x) != x) 
				goto close_go;
			bzero(buf, sizeof(buf));
			if(read(tcpfd,(char *)buf,sizeof(buf)) < 0)
				goto close_go;
		} else
		if(!strncmp(buf,"password",8)) {
			if(write(tcpfd,pprom,sizeof(pprom)) != sizeof(pprom))
				goto close_go;
			bzero(buf, sizeof(buf));
			if(read(tcpfd,(char *)buf,sizeof(buf)) < 0)
				goto close_go;
		} else {
			if(say(0,buf))
				goto close_go;
			continue;
		}
		if(strlen(buf) > 64) {
			if(sayn(0,toobig,sizeof(toobig)))
                                goto close_go;
			continue;
		}

		bzero(cbuf, sizeof(cbuf));
		sprintf(cbuf,"response '%s'",buf);
		if(auth_send(cbuf))
			goto lostconn;
		if(auth_recv(buf,sizeof(buf)))
			goto lostconn;
		if(strncmp(buf,"ok",2)) {
			if(say(0,buf))
				goto close_go;
			continue;
		}
		auth_close();
		syslog(LLEV,"authenticate user=%s",ubuf);
		strcpy(authuser,ubuf);
		if(buf[2] != '\0')
			return(say(0,&buf[2]));
		return(sayn(0,loggedin,sizeof(loggedin)));
	}
	auth_close();
	(void)sayn(0,toobad,sizeof(toobad));
	return(1);

lostconn:
	auth_close();
	(void)sayn(0,lostit,sizeof(lostit));
	return(1);

close_go:
	auth_close();
	return(1);
}


/* 
 * The following three subroutines (say, sayn, sayfile) are from rlogin-gw.c 
 */
/* Write to client */
static int
say(fd,s)
int     fd;
char    *s;
{
        return(sayn(fd,s,strlen(s)));
}



/* Write 'n' characters to client */
static int
sayn(fd,s,n)
int     fd;
char    *s;
int     n;
{
	if(write(fd,s,n) != n)
		return(1);
	return(write(fd,"\r\n",2) != 2);
}


/* Write file message to client */
sayfile(fd,fn)
int     fd;
char    *fn;
{
        FILE    *f;
	char    buf[BUFSIZ];
	char    *c;

	if((f = fopen(fn,"r")) == (FILE *)0)
		return(1);
	while(fgets(buf,sizeof(buf),f) != (char *)0) {
		if((c = index(buf,'\n')) != (char *)0)
			*c = '\0';
		if(say(fd,buf)) {
			fclose(f);
			return(1);
		}
	}
	fclose(f);
	return(0);
}


/*
 * Reads the inside client list 'client_list_file' to get the number 
 * of other inside clients participating in same conference (same 
 * multicast address/port) and their address/port.
 */
static void
read_inside_client_list(first)
int first;		/* TRUE if first time this routine called. */
{
	int fd, filelockfd;
	int i;
	static time_t lasttime = 0;
	struct stat statbuf;
	int err;

	/* Get file times */
	err = stat(client_list_file, &statbuf);
	if ((err < 0) && (errno != ENOENT)) {
		syslog(LLEV, "Error getting stat times of inside client list file %s: %m", 
				client_list_file);
		return;
	}
	else if (err >= 0) {
		/* If last modified time is before the last time checked, 
		   no need to read again */
		if (statbuf.st_mtime <= lasttime) {
       		 	time(&lasttime);  
			alarm(ALARM_SECONDS);
			return;
		}
	}

	if ((filelockfd = lock_client_list_file()) < 0) {
		/* If error locking file, write error in log, return, 
		   and don't start the alarm. */
		syslog(LLEV, "Error locking client list lock file %s: %m", 
				lock_client_file);
		return;
	}
	/* Open the inside client list file READONLY */
	if ((fd = open(client_list_file, O_RDONLY, 0400)) < 0) {
		unlock_client_list_file(filelockfd);
		/* If first time called and file not exists, ok */
		if ((errno == ENOENT) && (first == TRUE)) {
			alarm(ALARM_SECONDS);
			return;
		}
		syslog(LLEV, "Error opening inside client list file %s: %m", 
				client_list_file);
		return;
	}

	for (i=0; i<MAXHOSTS; i++) { 
		if (read(fd, &other_inside_client[i], sizeof(struct ic)) <=0) 
			break;
	}

       	time(&lasttime);  
	num_inside_clients = i;

	close(fd);
	unlock_client_list_file(filelockfd);
	/* start a timer to read it again */
	alarm(ALARM_SECONDS);
	return;
}


/* 
 * Add client to inside client list.
 */
static int
client_list_add(host, port, user) 
unsigned long host;
unsigned short port;
char *user;
{

	int fd, filelockfd;

	/* Lock the lock client file */
	if ((filelockfd = lock_client_list_file()) < 0) {
		syslog(LLEV, "Error locking lock client file: %m");
		return(-1);
	}

	if ((fd = open(client_list_file, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) {
			unlock_client_list_file(filelockfd);
			return(-1);
	}

	other_inside_client[num_inside_clients].addr = host;
	other_inside_client[num_inside_clients].port = port;
	strncpy(other_inside_client[num_inside_clients].user, user, MAXUSRSTR);

	if (write(fd, &other_inside_client[num_inside_clients],
				sizeof(struct ic))<=0) {
		close(fd);
		unlock_client_list_file(filelockfd);
		return(-1);	
	}
	num_inside_clients++;
	close(fd);
	unlock_client_list_file(filelockfd);

	/* Create UDP socket for sending to other inside clients */
	if (bind_udp_sock(&ic_sock,INADDR_ANY, 0,0,0,0) < 0){
		syslog(LLEV, "Error getting inside client socket: %m");
		return(-1);
	}

	return(0);
}


/* 
 * Remove client from inside client list.
 */
static void
client_list_remove(host, user)
unsigned long host;
char *user;
{
	int fd, filelockfd;
	int i;
	int found = -1;

	if ((filelockfd = lock_client_list_file()) < 0) {
		/* If error locking, log, then return */  
		syslog(LLEV, "Error locking lock client file: %m");
		return;
	}

	if ((fd = open(client_list_file, O_RDONLY, 0400)) < 0) {
		unlock_client_list_file(filelockfd);
		syslog(LLEV, "Error opening inside client list file %s: %m", 
				client_list_file);
		return;
	}

	for (i=0; i<MAXHOSTS; i++) { 
		if (read(fd, &other_inside_client[i], sizeof(struct ic)) <=0) 
			break;
		if (other_inside_client[i].addr == host &&
		    !strncmp(other_inside_client[i].user, user, MAXUSRSTR)) 
			found = i;
	}

	num_inside_clients = i;

	if (found == -1) {
		/* host not in client list */
		unlock_client_list_file(filelockfd);
		syslog(LLEV, "Host/User %s/%s not in inside client list", 
				host, user);
		return;	
	}

	close(fd);
	if (num_inside_clients == 1) { /* we are last client */
		if (unlink(client_list_file) < 0) {
			syslog(LLEV, "Cannot delete %s", client_list_file);
		}
		unlock_client_list_file(filelockfd);
		return;
	}
	fd = open(client_list_file, O_RDWR|O_TRUNC, 0600);
	for (i=0; i<num_inside_clients; i++) { 
		if (i==found) continue;
		if (write(fd, &other_inside_client[i], sizeof(struct ic)) <=0) 
			break;
	}

	num_inside_clients--;
	close(fd);
	unlock_client_list_file(filelockfd);
	return;
}


/* 
 * Every ALARM_SECONDS sig_alarm() is called, usually to update the 
 * inside client list.  It is also used to stop the proxy if
 * the timelimit is reached.
 */
static void
sig_alarm()
{
	static int num_seconds = 0;

	/* Check if there is a time limit for the proxy */
	if (timelimit != 0) {
		num_seconds += ALARM_SECONDS;
		if (num_seconds >= timelimit) {
			leaveflag++;
		}
	}
	read_inside_client_list(FALSE);
}


/* Used for debugging.  Start without inetd so you can run with debugger. */
/* Defined in Makefile */
#ifdef BINDDEBUG
int
get_tcp_ctrl_sock()
{
	int	ctrl_sock;		/* socket for client tcp connection */
	struct sockaddr_in l_sin;	/* local sin */
	int	tcp_sock;		/* socket created by accept */
	struct servent *serv_ent;	/* mbonesrv entry */

	/* Allocate a socket for communication with (inside) client */
	if ((ctrl_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		syslog(LLEV,"socket error");
		return(-1);
	}

	bzero((char *)&l_sin, sizeof(l_sin));
	l_sin.sin_family = AF_INET;

	/* Only accept incoming communication from inside the firewall */
	l_sin.sin_addr.s_addr = inet_addr(INSIDE_INTERFACE);
	l_sin.sin_port = htons(BINDDEBUGPORT);



	/* Bind the socket */
	if (bind(ctrl_sock, (struct sockaddr *)&l_sin, sizeof(l_sin)) < 0){
		syslog(LLEV,"bind error, debug port %d", BINDDEBUGPORT);
		return(-1);
	}

	/* Listen for connections on the socket */
	if (listen(ctrl_sock, 1) < 0) {
		syslog(LLEV,"listen error");
		return(-1);
	}
	if ((tcp_sock=accept(ctrl_sock, 0, 0))< 0){
		syslog(LLEV,"accept error");
		return(-1);
	}
	dup2(tcp_sock, 0);
	dup2(tcp_sock, 1);
	return(0);
}

#endif

