/*  QFXSERV.C	28-Feb-01 20:33:27 by Kevin D. Quitt

    QFX Quick File Exchange Server.

21-May-03 12:28, [ROCKYROAD]d:\isiarm\Sample Utilities\QFXServer\qfxserv.c, Edit by kdq
    Change stderr output to use TOCONSOLE, and set to stdout.  Flush all
    output.
*/
#define	VERSION	"2.00"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h> 
#include <sys/stat.h>
#include <ctype.h>


#ifdef	WIN32
#undef	WIN32
#define	WIN32	1
#endif

#ifdef	VMS
#undef	VMS
#define	VMS	1
#endif


#if	WIN32
#include <fcntl.h>
#include <io.h>
#include <conio.h>
#include <Winsock2.h>
#include <ctype.h>
#include <time.h>
#elif	VMS
#include <in.h>
#include <inet.h>
#include <netdb.h>
#include <types.h>
#include <socket.h>
#include <tcp.h>
#include <unixio.h>
#include <ucx$inetdef.h>
#include <iodef.h>
#include <unixlib.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <ctype.h>
#include <time.h>
#endif


#if WIN32
#define	CloseSocket(s)	closesocket(s)
typedef unsigned long	ulong;
#else
#define	CloseSocket(s)	close(s)
#endif

#define	TOCONSOLE   stdout

#define	TKNAME(a,b,c,d) (((d)<<24)|((c)<<16)|((b)<<8)|(a))

#include    "qfxdef.h"

#define	MAXHOSTNAME	(128)


#ifndef	TRUE
#define	TRUE	(1==1)
#define	FALSE	(1!=1)
#endif
#ifndef NUL
#define	NUL	( 0 )
#endif

#define	mkstr(t)    # t
#define	mkstrstr(t) mkstr(t)

int verbose	= 0;
int inputDump	= FALSE;

enum    cleanState
  {
    atDir, atDot, atDoubleDot, inFileName
  };

/*  Take the input path and filename and sanitize it.  Convert slashes to
    forward slashes, remove dot notation, prevent the path from straying
    above the current directory.

    The outstr buffer must be as long as the instr buffer (outstr may be
    the same as instr).
*/
int safeCleanPath( char *instr, char *outstr )
  {
    int     olen    = 0;
    char   *ip      = instr;
    char   *op      = outstr;
    int     ch;

    enum cleanState   state   = atDir;

    if ( !ip  ||  !op )
	return	0;

    while ( (ch = *ip++) )
      {
	if ( ch  ==  '\\' )
	    ch  = '/';
	else if ((ch  ==  ':' )  ||  !isprint( ch ))
	    continue;

	switch ( state )
	  {
	    case    atDir:
	      {
		if ( ch  ==  '.' )
		    state   = atDot;
		else if ( ch  !=  '/' )
		  {
		    *op++   = ch;
		    olen++;

		    state   = inFileName;
		  }

		break;
	      }

	    case    atDot:
	      {
		if ( ch  ==  '.' )
		    state   = atDoubleDot;
		else if ( ch  ==  '/' )
		    state   = atDir;
		else
		  {
		    *op++   = '.';
		    *op++   = (char)ch;
		    olen  += 2;

		    state   = inFileName;
		  }
		break;
	      }

	    case    atDoubleDot:
	      {
		if ( ch  ==  '/' )	/* "../"			    */
		  {
		    state   = atDir;

		    if ( olen  ==  0 )
			continue;	/* Already at start of output	    */

		    olen--;		/* Bypass trailing slash	    */
		    op--;
		    do
		      {
			olen--;
			if ( *(--op)  ==  '/' )
			    break;
		      } while ( olen );

		  }
		else
		  {
		    *op++   = '.';
		    *op++   = '.';
		    olen  += 2;

		    state   = inFileName;
		  }
		break;
	      }

	    case    inFileName:
	      {
		*op++   = ch;
		olen++;

		if ( ch  ==  '/' )
		    state   = atDir;

		break;
	      }
	  }
      }

    *op = 0;
    if ( state  ==  inFileName )
	return	olen;

    return -olen;
  }

/*		General purpose socket utilities, BSD-style.		    */

/*  Wait for a connection to occur on a socket created with establish()
*/
int get_connection( int s )
  {
    int t;				/* socket of connection		    */
    int		    addrlen;
    struct sockaddr addr    = {0};

    if ( verbose  >  1 )
      {
	fprintf( TOCONSOLE, "\nWaiting for connection on %d\n",s);fflush( TOCONSOLE );
      }

    t	= accept( s, &addr, &addrlen );
    if ( t  <=  0 )
      {
	if ( errno  ==  EINTR )		/* EINTR might happen on accept(),  */
	  {
	    if ( verbose )
	      {
		fprintf( TOCONSOLE, "Got EINTR\n" );fflush( TOCONSOLE );
	      }
	    t	= accept( s, &addr, &addrlen );
	  }
      }
    if ( t <= 0 )
      {
	fprintf( TOCONSOLE, "accept error %d\n", errno );fflush( TOCONSOLE );
	exit( EXIT_FAILURE );
      }

    if ( verbose  >  1 )
	fprintf( TOCONSOLE, "get_connection: accept returned %d\n", t );
    if ( verbose )
      {
	time_t    theTime;
	theTime = time( NULL );
	fprintf( TOCONSOLE, "%sGot connection from %d.%d.%d.%d on %d\n",
		 ctime( &theTime ),
		 (unsigned char)addr.sa_data[ 2 ],
		 (unsigned char)addr.sa_data[ 3 ],
		 (unsigned char)addr.sa_data[ 4 ],
		 (unsigned char)addr.sa_data[ 5 ],
		 t );
      }
    fflush( TOCONSOLE );

    return  t;
  }


/*  Code to establish a socket; originally from bzs@bu-cs.bu.edu
*/
int establish( unsigned short portnum )
  { 
    char   myname[ MAXHOSTNAME + 1 ];
    int    s;
    struct hostent     *hp;
    struct sockaddr_in	sa   = {0};

    int	    one	    = 1;
    char   *onep    = (char *)&one;

#if WIN32
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 2, 0 );       
    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 ) 
      {
	fputs( "A useable WinSock DLL not found", TOCONSOLE );fflush( TOCONSOLE );
        return -1;
      }
#endif

    gethostname( myname, MAXHOSTNAME );
    hp= gethostbyname( myname );	/* get our address info		    */
    if ( hp  ==  NULL )			/* we don't exist !?		    */
      {
	fputs( "gethostbyname failed", TOCONSOLE );fflush( TOCONSOLE );
	return	-1;
      }
    sa.sin_family   = hp->h_addrtype;	/* this is our host address	    */
    sa.sin_port	    = htons( portnum );	/* this is our port number	    */

    s	= socket( AF_INET, SOCK_STREAM, 0 );
    if ( verbose  >  1 )
      {
	fprintf( TOCONSOLE, "establish: socket is %d\n", s );fflush( TOCONSOLE );
      }
    if ( s  <  0 )
      {
	fputs( "socket failed", TOCONSOLE );fflush( TOCONSOLE );
	return	-1;
      }
    setsockopt( s, IPPROTO_TCP, TCP_NODELAY, onep, sizeof one );

    if ( bind( s, (struct sockaddr *)&sa, sizeof (struct sockaddr_in))  <  0 )
      {
	fputs( "bind failed", TOCONSOLE );fflush( TOCONSOLE );
	CloseSocket( s );
	return	-1;
      }

    listen( s, 1 );			/* max # of queued connects	    */
    if ( verbose  >  1 )
      {
	fprintf( TOCONSOLE, "establish: listening on %d for %d\n", s, 1 );fflush( TOCONSOLE );
      }
    return  s;
  }


/*  Read specified amount of data from the socket.
*/
int	sockread( int sock, char *buf, int len )
  {
    int	got, total;

    if ( !buf  ||  (len < 1))
	return	0;

    total   = 0;
    if ( verbose )
      {
	fprintf( TOCONSOLE, "sock read %d\n", len );fflush( TOCONSOLE );
      }
    while ( len )
      {
	got = recv( sock, buf, len, 0 );
	if ( verbose )
	  {
	    fprintf( TOCONSOLE, "%d ", got );fflush( TOCONSOLE );
	  }
	if ( got  <=  0 )
	    break;

	total	+= got;
	len	-= got;
	buf	+= got;
      }

    if ( verbose )
      {
	fprintf( TOCONSOLE, "total %d\n", total );fflush( TOCONSOLE );
      }
    return  total;
  }



/*  Send data through the socket.
*/
void	socksend( int sock, char *buf, int len, int debug )
  {
    int	ch;

/*  Dump what's being sent in various formats, as specified.
*/
    if ( verbose  &&  debug )
      {
	if ( !debug )
	  {
	    if ( len  ==  4 )
	      {
		fprintf( TOCONSOLE, "\nFile length: %ld\n", ntohl( *(long *)buf));fflush( TOCONSOLE );
		return;
	      }
	    fprintf( TOCONSOLE, "(%d bytes of the file)\n", len );fflush( TOCONSOLE );
	    return;
	  }

	fprintf( TOCONSOLE, "%d >", len );fflush( TOCONSOLE );
	for ( ch = 0; ch < len; ch++ )
	  {
	    if ((debug  ==  1)  &&  isprint( buf[ ch ]))
	      {
		fputc( buf[ ch ], TOCONSOLE );fflush( TOCONSOLE );
	      }
	    else
	      {
		fprintf( TOCONSOLE, "\\x%02X", buf[ ch ] & 0xFF );fflush( TOCONSOLE );
	      }
	  }
	fprintf( TOCONSOLE, ">\n" );fflush( TOCONSOLE );
      }

    while ( len )
      {
	if ( verbose  >  1 )
	  {
	    fprintf( TOCONSOLE, "socksend %d\n", len );fflush( TOCONSOLE );
	  }
	ch  = send( sock, buf, len, 0 );
	if ( ch  >=  0 )
	  {
	    if ( verbose  &&  !debug )
	      {
		fprintf( TOCONSOLE, "Sent %d\n", ch );fflush( TOCONSOLE );
	      }
	    len	-= ch;
	    buf	+= ch;
	  }
	else
	  {
	    if ( verbose )
	      {
		fprintf( TOCONSOLE, "Error %d\n", ch );fflush( TOCONSOLE );
	      }
	    len	= 0;
	  }
      }
  }


/*  Send a file directly in the stream for the response.
*/
int sendfile( int sock, char *fnamebuf )
  {
    FILE   *imm;
    size_t  fsize;
    char   *fbuff;
    size_t  xfer    = 0;

    if ( !fnamebuf )
	return	0;

    imm = fopen( fnamebuf + 1, "rb" );
    if ( imm )
      {
	fseek( imm, 0, SEEK_END );
	fsize   = ftell( imm );		/* How big is the file?		    */
	if ( !fsize )
	  {
	      forgetit:
	    fclose( imm );
	    return  0;
	  }
	fseek( imm, 0, SEEK_SET );

	fbuff   = malloc( fsize );
	if ( !fbuff )
	    goto    forgetit;

/*  Read in the file.  If it works, send the file name, the four-byte file
    size, then the file.
*/
	xfer	= fread( fbuff, 1, fsize, imm );
	if ( xfer  ==  fsize )
	  {
	    socksend( sock, fnamebuf, strlen( fnamebuf ) + 1, 1 );

	    xfer    = htonl( fsize );
	    socksend( sock, (char *)&xfer, sizeof xfer, 2 );

	    socksend( sock, fbuff, fsize, 0 );

	    xfer    = 1;
	  }
	else
	  {
	    if ( verbose )
	      {
		fprintf( TOCONSOLE, "Read failed\n" );fflush( TOCONSOLE );
	      }
	    xfer    = 0;
	  }

	fclose( imm );
	free( fbuff );
      }
    else if ( verbose )
      {
	fprintf( TOCONSOLE, "Can't open [%s]\n", fnamebuf + 1 );fflush( TOCONSOLE );
      }

    return  xfer;
  }


void	sendQfxErrr( int socket )
  {
    QFXHEADER	qfxh;

    qfxh.length	= htonl( sizeof (QFXHEADER));
    qfxh.token	= QFX_ERRR;
    socksend( socket, (char *)&qfxh, sizeof (QFXHEADER), TRUE );
  }


int	qfxStat( char *filespec, char *statinfo )
  {
    int	len = 0;

    struct stat st  = {0};
    struct tm   lt  = {0}, *ltp;

    if ( stat( filespec, &st ) )
      {
	if ( verbose )
	  {
	    fprintf( TOCONSOLE, "(failed)\n" );fflush( TOCONSOLE );
	  }
	return	0;
      }

    ltp = localtime( &st.st_mtime );
    if ( ltp )
	lt	= *ltp;
    else
      {
	if ( verbose )
	  {
	    fprintf( TOCONSOLE, "(localtime failed)\n" );fflush( TOCONSOLE );
	  }
	return	0;
      }

    len	= sprintf( statinfo, "%04d%02d%02d%02d%02d%02d %ld",
		   lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
		   lt.tm_hour, lt.tm_min, lt.tm_sec,
		   st.st_size );
    statinfo[ len++ ]	= 0;

    if ( verbose )
      {
	fprintf( TOCONSOLE, "%s\n", statinfo );fflush( TOCONSOLE );
      }

    return  len;
  }

char   *HelpText
    =	"\nSample QFX protocol server, " VERSION "\n"
	"\nqfxserv [options]\n\n"
	"Options\n"
	"-i           Show contents of incoming packets.\n"
	"-v           Print extra information about transactions.\n"
	"             May be used more than once for even more info.\n"
	;

void	processCommandLine( int argc, char *argv[] )
  {
    int	    arg, c;

    for ( arg = 1; arg < argc; arg++ )
      {
	c   = argv[ arg ][ 0 ];
	if ((c  ==  '-')  ||  (c  ==  '/'))
	  {
	    c   = toupper( argv[ arg ][ 1 ]);

	    switch ( c )
	      {
		case	'I':
		  {
		    inputDump	= TRUE;
		    fprintf( TOCONSOLE, "Dumping input packets\n" );
		    fflush( TOCONSOLE );
		    break;
		  }

		case	'V':
		  {
		    if ( verbose++ )
			fprintf( TOCONSOLE, "Even more verbose\n" );
		    else
			fprintf( TOCONSOLE, "Verbose\n" );
		    fflush( TOCONSOLE );
		    break;
		  }

		case    '?':
		  {
  help:
		    fputs( HelpText, TOCONSOLE );
		    exit( EXIT_SUCCESS );
		  }

		default:
		  {
		    fprintf( TOCONSOLE, "Unknown: %s, use ? for help\n",
			     argv[ arg ]);
		    goto    help;
		  }
	      }
	  }
	else
	    goto    help;
      }
  }


void	dumpPacket( char *buf, int count )
  {
    int	ch;

    if ( !inputDump  ||  !buf )
	return;

    if ( count  <=  0 )
      {
	fprintf( TOCONSOLE, "Bad count: %d\n", count );fflush( TOCONSOLE );
	return;
      }

    fprintf( TOCONSOLE, "%d: ", count );

    for ( ch = 0; ch < count; ch++ )
      {
	if ( isprint( buf[ ch ]))
	    fputc( buf[ ch ], TOCONSOLE );
	else
	    fprintf( TOCONSOLE, "\\x%02X", buf[ ch ] & 0xFF );
      }
    fputs( "\n", TOCONSOLE );
    fflush( TOCONSOLE );
  }


char rcv[ 2048 ];
char xmit[ 2048 ];

int main( int argc, char* argv[])
  {
    int	    t, c, x, len;
    int	    listenSocket    = 0;
    int	    cnt		    = -1;

    QFXPACKET *ipkt   = (QFXPACKET *)&rcv;
    QFXPACKET *opkt   = (QFXPACKET *)&xmit;


    processCommandLine( argc, argv );

    listenSocket   = establish( QFXPORT );
    if ( verbose  >  1 )
      {
	fprintf( TOCONSOLE, "establish returned %d\n", listenSocket );fflush( TOCONSOLE );
      }
    if ( listenSocket  <=  0 )
	exit( EXIT_FAILURE );

    t	= 0;
    while ( TRUE )
      {
	memset( rcv, 0, sizeof rcv );	/* Setwardship			    */
	if ( t )			/* Close last comm socket	    */
	  {
	    if ( verbose )
	      {
		fprintf( TOCONSOLE, "Closing connection %d\n", t );fflush( TOCONSOLE );
	      }
	    CloseSocket( t );
	    t	= 0;
	  }
	if ( verbose  ==  1 )
	  {
	    fprintf( TOCONSOLE, "\nWaiting for connection\n" );fflush( TOCONSOLE );
	  }
	t   = get_connection( listenSocket );
	cnt++;				/* Count the number of connects	    */


/*  Read the length of the packet, then the rest of the packet.
*/
	c   = sockread( t, rcv, 4 );
	dumpPacket( rcv, c );
	if ( c  !=  4 )
	  {
	    if ( verbose )
	      {
		fprintf( TOCONSOLE, "Error on read: 4 - %d\n", c );fflush( TOCONSOLE );
	      }
	    continue;
	  }

	c   = ntohl( ipkt->header.length );
	if ( verbose > 1 )
	  {
	    fprintf( TOCONSOLE, "Packet length is %d\n", c );fflush( TOCONSOLE );
	  }
	x   = sockread( t, (char *)&ipkt->header.token, c - 4 );
	dumpPacket( (char *)&ipkt->header.token, x );
	if ( x  !=  c - 4 )
	  {
	    if ( verbose )
	      {
		fprintf( TOCONSOLE, "Error on read: %d - %d\n", c - 4, x );fflush( TOCONSOLE );
	      }
	    continue;
	  }

	switch ( ipkt->header.token )
	  {
	    case    QFX_INFO:
	      {
		if ( verbose )
		  {
		    fprintf( TOCONSOLE, "Info request for %s: ", ipkt->body.info.name );fflush( TOCONSOLE );
		  }
		safeCleanPath( ipkt->body.info.name, ipkt->body.info.name );

		len = qfxStat( ipkt->body.info.name, opkt->body.info.name );
		if ( len  ==  0 )
		  {
		    sendQfxErrr( t );
		    break;
		  }

		len += sizeof (QFXHEADER);
		opkt->header.length = htonl( len);
		opkt->header.token  = QFX_INFO;

		socksend( t, (char *)opkt, len, FALSE );
		break;
	      }

	    case  QFX_SEND:
	      {
		struct stat st  = {0};
		int	    ret, ByteRead;
		QFXPACKET  *fpkt;
		FILE	   *f;

		if ( verbose )
		  {
		    fprintf( TOCONSOLE, "Send request for %s: ", ipkt->body.info.name );fflush( TOCONSOLE );
		  }
		safeCleanPath( ipkt->body.info.name, ipkt->body.info.name );

		ret = stat( ipkt->body.info.name, &st );
		if ( ret )
		  {
		    if ( verbose )
		      {
			fprintf( TOCONSOLE, "(failed)\n" );fflush( TOCONSOLE );
		      }
		    sendQfxErrr( t );
		    continue;
		  }

		len	= st.st_size + sizeof (QFXHEADER);
		fpkt	= malloc( len );

		fpkt->header.length = htonl( len );
		fpkt->header.token  = QFX_SEND;
		f   = fopen( ipkt->body.info.name, "rb" );
		ByteRead = fread( fpkt->body.info.name, sizeof( char ), len - sizeof (QFXHEADER), f );
		if ( verbose )
		  {
		    fprintf( TOCONSOLE, "ByteRead = %d: \n", ByteRead );fflush( TOCONSOLE );
		  }
                
		fclose( f );
		socksend( t, (char *)fpkt, len, FALSE );
		free( fpkt );

		break;
	      }


	    case  QFX_DIFF:
	      {
		int	same;
		char   *dp;

		dp  = ipkt->body.info.name + strlen( ipkt->body.info.name ) + 2;

		if ( verbose )
		  {
		    fprintf( TOCONSOLE, "Diff request for %s @%s: ",
			     ipkt->body.info.name, dp );fflush( TOCONSOLE );
		  }
		safeCleanPath( ipkt->body.info.name, ipkt->body.info.name );

		if ( !qfxStat( ipkt->body.info.name, xmit ))
		  {
		    if ( verbose )
		      {
			fprintf( TOCONSOLE, "(stat failed)\n" );fflush( TOCONSOLE );
		      }
		    sendQfxErrr( t );
		    break;
		  }

		same	= !strcmp( dp, xmit );

		len = sizeof (QFXHEADER) + 2;
		opkt->header.length = htonl( len );
		opkt->header.token  = QFX_DIFF;
		opkt->body.diff.stamp[ 0 ]  = same ? 'T' : 'F';
		opkt->body.diff.stamp[ 1 ]  = 0;
		socksend( t, (char *)opkt, len, FALSE );

		break;
	      }



	    case  QFX_ERRR:
	      {
		if ( verbose )
		  {
		    fputs( "ERRR packet received\n", TOCONSOLE );
		    fflush( TOCONSOLE );
		  }
		break;			/* Drop it on the floor		    */
	      }


	    default:
	      {
		if ( verbose )
		  {
		    int	ch;
		    fprintf( TOCONSOLE, "unknown packet\n" );fflush( TOCONSOLE );
		    len = ntohl( ipkt->header.length );
		    for ( ch = 0; ch < len; ch++ )
		      {
			if ( isprint( rcv[ ch ] ))
			    fputc( rcv[ ch ], TOCONSOLE );
			else
			    fprintf( TOCONSOLE, "\\x%02X", rcv[ ch ] & 0xFF );
		      }
		    fprintf( TOCONSOLE, "\n" );
		    fflush( TOCONSOLE );
		  }
		sendQfxErrr( t );
		break;
	      }
	  }
      }

#if WIN32
    WSACleanup( );
#endif

    return EXIT_SUCCESS;		/* To keep lint happy		    */
  }
