/* THE REALAUDIO FIREWALL PROXY KIT 2.0 BETA 2 */
/* This file: $Date: 1996/04/17 18:25:22 $ $Revision: 1.5 $ */

/*
 * Copyright (c) 1996 Progressive Networks, Inc.
 * 
 * By clicking on the "Accept" button or opening the package, you are
 * consenting to be bound by this Agreement.  If you do not agree to all of
 * the terms of this agreement, click the "Do Not Accept" button and, if
 * you received the Software by package, return the product to the place of
 * purchase for a full refund.
 * 
 * 1. GRANT OF LICENSE.
 * 
 *  
 * Progressive Networks, Inc. (PN) grants to Recipient a limited,
 * non-exclusive, royalty-free license to use, modify, or redistribute the
 * Software without a fee, subject to the conditions stated below.
 * 
 * All other rights are reserved to PN. 
 * 
 * 
 * 2.  CONDITIONS
 * The above grant of license is subject to the following conditions:
 * 
 *        A) The copyright notice ("Copyright (c) 1996 Progressive
 *           Networks, Inc. ")  and this license appear on all copies of the
 *           Software and/or any derivatives of the Software.
 * 
 *        B) Recipient uses the Software or any derivatives only to build a
 *           firewall proxy for use with RealAudio products from PN.
 * 
 *        C) Recipient acknowledges and agrees that PN is and shall be the 
 *           exclusive owner of all right, title and interest, including
 *           copyright, in the Software.
 * 
 * 3. SOFTWARE  MAINTENANCE. 
 * 
 * PN is not obligated to provide maintenance or updates to Recipient for
 * the Software. However, any maintenance or updates provided by PN shall
 * be covered by this Agreement.
 * 
 * 4. DISCLAIMER OF WARRANTY.
 * 
 * Software is deemed accepted by Recipient.  Because PN is providing
 * Recipient the Software for free, the Software is provided to Recipient
 * AS IS, WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED
 * BYAPPLICABLE LAW, PROGRESSIVE NETWORKS FURTHER DISCLAIMS ALL
 * WARRANTIES,INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
 * NONINFRINGEMENT. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE
 * OF THE SOFTWARE REMAINS WITH RECIPIENT. TO THE MAXIMUM EXTENT PERMITTED
 * BY APPLICABLE LAW, IN NO EVENT SHALL PROGRESSIVE NETWORKS OR ITS
 * SUPPLIERS BE LIABLE FOR ANY CONSEQUENTIAL, INCIDENTAL, DIRECT, INDIRECT,
 * SPECIAL, PUNITIVE, OR OTHER DAMAGES WHATSOEVER (INCLUDING, WITHOUT
 * LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION,
 * LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF
 * THIS AGREEMENT OR THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF
 * PROGRESSIVE NETWORKS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGES. BECAUSE SOME STATES/JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR
 * LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE
 * ABOVE LIMITATION MAY NOT APPLY TO RECIPIENT.
 * 
 * 5. INDEMNIFICATION. 
 * 
 * Recipient hereby agrees to defend, indemnify, and hold PN, its
 * directors, officers, employees and agents, harmless from any and all
 * claims, damages, and expenses (including attorneys fees and costs) of
 * any nature arising out of the use, modification, or redistribution the
 * Software or any derivatives.
 * 
 * 6. U.S. GOVERNMENT RESTRICTED RIGHTS AND EXPORT RESTRICTIONS. 
 * 
 * The Software is provided with RESTRICTED RIGHTS. Use, duplication, or
 * disclosure by the Government is subject to restrictions as set forth in
 * subparagraph (c)(1)(ii) of The Rights in Technical Data and Computer
 * Software clause of DFARS 252.227-7013 or subparagraphs (c)(i) and (2) of
 * the Commercial Computer Software-Restricted Rights at 48 CFR 52.227-19,
 * as applicable. Manufacturer is Progressive Networks, 616 First Avenue,
 * Suite 701, Seattle, Washington 98104-2258. Recipient acknowledges that
 * none of the Software or underlying information or technology may be
 * downloaded or otherwise exported or re-exported (i) into (or to a
 * national or resident of) Cuba, Iraq, Libya, Yugoslavia, North Korea,
 * Iran, Syria or any other country to which the U.S. has embargoed goods;
 * or (ii) to anyone on the U.S. Treasury Department's list of Specially
 * Designated Nationals or the U.S. Commerce Department's Table of Denial
 * Orders.  By using the Software, you are agreeing to the foregoing and
 * you are representing and warranting that you are not located in, under
 * the control of, or a national or resident or resident of any such
 * country or on any such list.
 * 
 * 7. GOVERNING LAW; ATTORNEYS FEES. 
 * 
 * This agreement shall be governed by the laws of the State of Washington
 * and Recipient further consents to jurisdiction by the state and federal
 * courts sitting in the State of Washington. If either PN or Recipient
 * employs attorneys to enforce any rights arising out of or relating to
 * this Agreement, the prevailing party shall be entitled to recover
 * reasonable attorneys' fees.
 * 
 * 8. ENTIRE AGREEMENT. 
 * 
 * This agreement constitutes the complete and exclusive agreement between
 * PN and Recipient with respect to the subject matter hereof, and
 * supersedes all prior oral or written understandings, communications or
 * agreements not specifically incorporated herein.  This agreement may not
 * be modified except in a writing duly signed by an authorized
 * representative of PN and Recipient.
 *
 */
/*
 *  Real Audio Transparent Proxy Version 1 Sample Code.
 *
 *  This code assumes that there is someway for this program
 *  to determine where the original connection was going.
 *   ( See: "**** SYSTEM DEPENDENT *****" below)
 *
 *  - we assume we're being called from inetd or in some way that
 *	the control connection is on fd 0.  
 *  -  figure out what server/port we were trying to connect to
 *		(this is OS/FW dependent)
 *  -  Read/write startup & options messages from player to server.
 *  -  Setup control connection to the "real" server.
 *  -  For non tcp connections, setup UDP backchannel passthrough stream.
 *  -  Copy control data to/from player/server
 *  -  For udp, copy data stream from server to player.
 */

/* use tabstop=4 */
#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/socket.h>
#include	<netdb.h>
#include	<netinet/in.h>
#include	<sys/signal.h>
#include	"tproxy.h"


#ifdef SYSV
/* if you don't have bzero/bcopy, define SYSV. */
#define bcopy(src, dest, size) memcpy(dest, src, size)
#define bzero(buf,size) memset(buf, '\0', size);
#endif /* SYSV */


#define DEBUG

/* socket to audio server */
int serverfd;

/* udp sockets for audio */
int serveraudiofd, playeraudiofd;
/* client/player address for udp stream */
struct sockaddr_in playerinfo;

main(argc, argv)
int argc;
char **argv;
{

	int 		count;
	fd_set		readfds, dummy;
	char		buf[2048];
	struct		timeval *timeout = (struct timeval *)0;
	int			use_tcp;

     
#ifdef DEBUG
	/* 
	 * define DEBUG if you want to run this via a debugger 
	 * SEE debugit() below.
	 */
	debugit();
#endif /* DEBUG */

	/* 
	 * do initial "setup dialog" with client and connect to audio server
	 *  (globally sets serverfd)
	 * if udp backchannel connection is required then, backchannel is made 
	 *  and globally sets, playeraudiofd, serveraudiofd and playerinfo)
	 */
	if ( (use_tcp = setupdialog()) < 0 ) {
		/* error? */
		close(0);
		if ( serverfd )
			close(serverfd);
		exit(1);
	}

	/* 
	 * Main loop
	 *  - we've done all the setup
	 *  - we have descriptors for sending/receiving control info
	 *    to/from player/server 
	 *	  - 0 is control socket to/from player (setup by inetd)
	 *	  - serverfd is control socket to/from server (setup in setupdialog)
	 *  - for udp connections we also have descriptors for 
	 *    - sending/receiving audio info. (setup in udpsetup)
	 *	  - playeryaudiofd is socket for sending udp audio
	 *	  - serveraudiofd is socket for receiving udp audio
	 *    - playerinfo has address info for udp "write"
	 *
	 *  - Now just sit and wait for info and copy it to the
	 *    appropriate place.
	 */

	FD_ZERO(&dummy);
	FD_SET(0, &dummy);
	FD_SET(serverfd, &dummy);
	if (use_tcp == 0 ) {
		FD_SET(serveraudiofd, &dummy);
	}
	while (1) {
		struct 		sockaddr_in from;
		int 		fromlen = sizeof(from);

		(void) bcopy( &dummy, &readfds, sizeof(fd_set) );
		if ( select(FD_SETSIZE, &readfds, (fd_set *)0, (fd_set *)0,timeout) <= 0) {
			/* syslog timeout goes here */
			break;
		}
		/*  read/write control info (and sometimes audio) from server to player */
		if ( FD_ISSET(serverfd, &readfds) ) {
			if ( (count = read( serverfd, buf, sizeof(buf))) <= 0 )
				break;
			if ( write(0, buf, count) != count )
				break;
		}
		/*  read/write control info from player to server */
		if ( FD_ISSET(0, &readfds) ) {
			if ( (count = read(0, buf, sizeof(buf)) ) <= 0 )
				break;
			if ( write(serverfd, buf, count) != count )
				break;
		}
		if (use_tcp == 0 ) {
				/* write audio data from server to player */
				if ( FD_ISSET(serveraudiofd, &readfds) ) {
				/* 
				 * you may want to check the source/from address to see if
				 * the stream is coming from the "right" place.  Given
				 * this is udp it could be easily spoofed. (All
				 * you would get is "garbage" in the audio stream).
				 */
				if( (count = recvfrom(serveraudiofd, buf, sizeof(buf),
				 			0, (struct sockaddr *)&from, &fromlen) ) <= 0 ) {
					break;
				}
				if( sendto(playeraudiofd, buf, count, 0, 
				 (struct sockaddr *)&playerinfo, sizeof(playerinfo)) != count )
					break;
				}
		}
	}
	exit(0);
}

/*
 *  Read startup/options messages from player.
 *
 */
int
setupdialog()
{
	unsigned int opcode;
	unsigned char connecttype = 1; /* start off assuming TCP */
	unsigned short serverport, backportoffset = 0;
	short msgbytes; 
	char version = 0;
	int done;
	char hostname[128], versionstring[4];
	char localbuf[2048];   
	void timeout();
	extern int errno;

	/* setup a timeout in case dialog doesn't finish */
	signal(SIGALRM, timeout);
	alarm(RA_TIMEOUT);

	/*
	 * **** SYSTEM DEPENDENT ****
	 * figure out which server/port we were trying to connect to.
	 * This is system/FW dependent and we just use local variables/info
	 * for now.  Vendors should add their appropriate calls in here,
	 * and modify the call to (and subroutine) serverconnect() as 
	 * appropriate.
	 */
	strcpy(hostname, "bsd.prognet.com");
	serverport = 7070;
	/* Can we connect to the server */
	if ( (serverfd = serverconnect(hostname, serverport)) < 0 ) {
		/* errno may still be set from previous call */
		/* syslog error? */
		return(-1);
	}


	/* 
	 * read startup info from client, check some of it and copy it back out.
	 *	We look for: 
 	 *		- String "PNA"
	 *		- 2 byte version number (currently 5) in network byte order.
	 *		- option info (backchannel port, udp or tcp connection, etc.)
	 */

	/* check if first info is string "PNA" followed by version number */
	if ( read(0, &versionstring, 3) != 3) {
		/* syslog error? */
		return(-1);
	}
	if ( strncmp(versionstring, "PNA", 3) !=  0 ) {
		/* syslog error? */
		return(-1);
	}
	if ( write(serverfd, &versionstring, 3) != 3) {
		/* syslog error? */
		return(-1);
	}
	/* ignore first byte of version for now */
	if ( read( 0, &version, 1) != 1 ) {
		/* syslog error? */
		return(-1);
	}
	if ( write( serverfd, &version, 1) != 1 ) {
		/* syslog error? */
		return(-1);
	}
	if ( read( 0, &version, 1) != 1 ) {
		/* syslog error? */
		return(-1);
	}
	/* check for version 5 or higher */
	if ( (version) < RA_VERSION ) {
		/* syslog error? */
		return(-1);
	}
	if ( write( serverfd, &version, 1) != 1 ) {
		/* syslog error? */
		return(-1);
	}

	done = 0;
	while ( !done ) {

		switch(getopcode(&msgbytes))
		{
			case RA_BACKPORTOFFSET:
				if ( read( 0, &backportoffset, msgbytes) != msgbytes ) {
					/* syslog error? */
					return(-1);
				}
				/* setup backport connection and write out "new" dataport. */
				if ( udpsetup(backportoffset) < 0 ) {
					/* syslog error? */
					return(-1);
				}
				connecttype = 0;  /* UDP connection */
				break;
			case RA_END:
				/* got all the info we need?  If not, send error */
					done = 1;
					break;
				
			default:
				/* just read option info and write it out */
				/* 
				 * if you're worried about overflows of this buffer (which
				 * might be possible if someone tries to spoof the protocol)
				 * then use malloc. 
				 */
				if ( read( 0, &localbuf, msgbytes) != msgbytes ) {
					/* syslog error? */
					return(-1);
				}
				if ( read( serverfd, &localbuf, msgbytes) != msgbytes ) {
					/* syslog error? */
					return(-1);
				}
				break;
		}
	}

	alarm(0);  /* "turn-off" alarm */
	return(connecttype);
}

void timeout()
{
 /* syslog error? */
 exit(1);
}



#include <ctype.h> 
/*
 * Connect to a server
 *
 *  Args:
 * 	Hostname:  null terminated string with name/IP address
 *	Port:	   port number to connect to.
 * 
 *  Return Value:  file descriptor for connection
 *			or 
 *		-1 for various errors 
 *		(Should probably have multiple errors for different
 *			problems)
 *		
 */
int
serverconnect(hostname,port)
char *hostname;
int port;
{
	char		*p;
	struct hostent		*hp;
	struct sockaddr_in	addr;
	int			fd;
	unsigned long 		ipaddr;


	/* try and check if hostname is an IP address (only digits and dots) */
	p = hostname;
	while ( (*p != '\0') && (*p == '.' || isdigit(*p)) )
		p++;
	/* if IP address */
	if ( *p == '\0' ) {
		/* "convert" IP address from string to netaddr */
		if ( (ipaddr = inet_addr(hostname)) == -1L ) {
			return(-1);
		}
		(void) bcopy((char *)&ipaddr, (char *)&addr.sin_addr, sizeof(addr));
	} else {
		if ( (hp = gethostbyname(hostname)) == (struct hostent *)0 )
			return(-1);
		(void) bcopy(hp->h_addr, (char *)&addr.sin_addr, hp->h_length);
	}
	addr.sin_port = htons(port);
	addr.sin_family = AF_INET;
	/* setup socket */
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if ( fd < 0 ) {
		return(-1);
	}
	/* connect to server - errno will still be set if we need it later */
	if ( connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) {
		return(-1);
	}
	return(fd);
}

/*
 * setup udp backchannel socket for passthrough from server to client 
 * we also send server the port number it should use to send audio
 *	data back to the proxy.
 */
int
udpsetup(player_audioport)
short player_audioport;
{
	int		size, count, temp;
	struct sockaddr_in	addr, info;

	/* setup bind/listen etc for udp backchannel from server */
	if ( (serveraudiofd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 ) {
		/* syslog  error */
		return(-1);		/* Probably out of sockets */
	}
	(void) bzero (&addr, sizeof (addr));
	addr.sin_family = AF_INET;
	/*
	 * setup port to listen for RA stream.
	 * if you want to only use a certain range of ports add your code here.
	 */
	addr.sin_port = htons(0);
	if ( bind(serveraudiofd, (struct sockaddr *)&addr, sizeof (addr)) < 0 ) {
		/* syslog error ? */
		return(-1);
	}

	/* lets get socket info so later we can find out our local port */
	size = sizeof(info);
	if ( getsockname(serveraudiofd, (struct sockaddr *)&info, &size)  < 0 ) {
		/* syslog error ? */
		return(-1);
	}
	/* send server the port number it should use to talk to us */
	if ( write(serverfd, &info.sin_port, 2) != 2) {
		/* syslog error ? */
			return(-1);
	}
	/* create socket for talking to player audio port */
	if ( (playeraudiofd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 ) {
		/* syslog error */
		return(-1);		
	}
	/* get inet info of client/player - we need this later  */
	size = sizeof(playerinfo);
	if ( getpeername(0, (struct sockaddr *)&playerinfo, &size) < 0 ) {
		/* syslog error ? */
		return(-1);
	}
	/* now point back to audio port on player */
	playerinfo.sin_port = player_audioport;
	return(0);
}


/*
 * get startup protocol opcode from control channel
 *
 * Args: msgbytes - pointer to store number of bytes in message
 *			stream.
 *
 * Returns: -1 on error
 *		    opcode that was read
 */
int
getopcode(msgbytes)
short *msgbytes;
{
	unsigned short opcode = 0;

	*msgbytes = 0;
	/* read option opcode from client */
	if ( read( 0, &opcode, 2) != 2 ) {
		return(-1);
	}
	/* write option opcode to server */
	if ( write(serverfd, &opcode, 2) != 2 ) {
		return(-1);
	}
	/* End of options has no more info */
	if ( ntohs(opcode) == RA_END )
		return(RA_END);
	/* read byte count from client */
	if ( read(0, msgbytes, 2) != 2 ) {
		return(-1);
	}
	/* write bytecode to server */
	if ( write(serverfd, msgbytes, 2) != 2 ) {
		return(-1);
	}
	/* 
	 * convert info to local byte order
	 */
	*msgbytes = ntohs(*msgbytes);
	return(ntohs(opcode));
}

#ifdef	DEBUG
/*
 * Define DEBUG and set the define below to the port
 * that you want the proxy to bind to.  This can be useful
 * for running the proxy with a debugger.
 * 
 */
#define DEBUGPORT	5556
debugit()
{
	struct	sockaddr_in	addr;
	int	sock, newfd;

	if( (sock = socket(AF_INET,SOCK_STREAM,0)) < 0 ) {
		perror("socket problem");
		exit(1);
	}
	bzero(&addr,sizeof(addr));
	addr.sin_port = htons(DEBUGPORT);
	addr.sin_family = AF_INET;
	if( bind( sock, (struct sockaddr *)&addr, sizeof(addr)) ) {
		perror("bind problem");
		exit(1);
	}
	if( listen(sock, 1) < 0 ) {
		perror("listen problem");
		exit(1);
	}
	if( ( newfd=accept(sock, 0, 0) ) < 0 ) {
		perror("accept");
		exit(1);
	}
	/* close stdin/out and dup to socket we just setup */
	close(0);
	dup(newfd);
	close(1);
	dup(newfd);
}

#endif /* DEBUG */

