/***************************************************************************
 * LPRng - An Extended Print Spooler System
 *
 * Copyright 1988-2001, Patrick Powell, San Diego, CA
 *     papowell@lprng.com
 * See LICENSE for conditions of use.
 *
 ***************************************************************************/

 static char *const _id =
"$Id: monitor.c,v 1.8 2004/09/24 20:18:14 papowell Exp papowell $";



#include "ifhp.h"
/**** ENDINCLUDE ****/

/*
 * Monitor for TCP/UDP data
 *  Opens a UDP or TCP socket and waits for data to be sent to it.
 *
 *  monitor [-t] [-u] [port]
 *   port is an integer number or a service name in the services database
 *   default is to use UDP.
 */


int udp_open_listen( int port );
int tcp_open_listen( int port );

void Decode( char *in );

char buffer[1024*64];
int use_tcp = 1;
int use_udp = 1;
int port_num = 2001;
int udp_fd = -1;
int tcp_fd = -1;
fd_set readfds;
fd_set testfds;
int OF_Mode;

char *Name = "???";
int Status_fd = -1;
int Max_status_size, Min_status_size;
int Trace_on_stderr = 1, Full_time;
int Debug;
int Errorcode;
char *Statusfile;

/*****************************************************************
 * Command line options and Debugging information
 * Getopt is a modified version of the standard getopt(3) command
 *  line parsing routine. See getopt.c for details
 *****************************************************************/

/* use this before any error printing, sets up program Name */

struct info {
	char *buffer;
	int len;
	int max_len;
};

struct info *inbuffers;
int max_in_buffers;

void Add_buffer( int n )
{
	int len = max_in_buffers, count;

	if(Debug)fprintf(stderr, "Add_buffer: start n %d, inbuffers 0x%lx, max_in_buffers %d\n",
		n, Cast_ptr_to_long(inbuffers), max_in_buffers );
	if( max_in_buffers <= n ){
		max_in_buffers = n+1;
		count = sizeof(inbuffers[0])*max_in_buffers;
		inbuffers = realloc( inbuffers, count );
		if( inbuffers == 0 ){
			fprintf(stderr,"Add_buffer: realloc %d failed\n", n );
			exit(1);
		}
		for( count = len; count < max_in_buffers; ++count ){
			memset(inbuffers+count,0,sizeof(inbuffers[0]));
		}
	}
	if(Debug)fprintf(stderr,"Add_buffer: end n %d, inbuffers 0x%lx, max_in_buffers %d\n",
		n, Cast_ptr_to_long(inbuffers), max_in_buffers );
}

void Clear_buffer( int n )
{
	struct info *in;
	if(Debug)fprintf(stderr,"Clear_buffer: n %d\n", n );
	Add_buffer( n );
	in = inbuffers+n;
	in->len = 0;
}

struct info *Save_outbuf_len( int n,  char *str, int len )
{
	struct info *in;

	if(Debug)fprintf(stderr,"Save_outbuf_len: n %d, len %d\n", n, len );
	Add_buffer(n);
	in = inbuffers+n;
	if(Debug)fprintf(stderr,
		"Save_outbuf_len: start inbuffers 0x%lx, in 0x%lx, buffer 0x%lx, len %d, max_len %d\n",
		Cast_ptr_to_long(inbuffers), Cast_ptr_to_long(in),
		Cast_ptr_to_long(in->buffer), in->len, in->max_len );
	if( len + in->len >= in->max_len ){
		in->max_len += len;
		in->buffer = realloc( in->buffer, in->max_len+1 );
		if( in->buffer == 0 ){
			fprintf(stderr,"Put_oubuf_len: realloc %d failed\n", in->max_len );
			exit(1);
		}
	}
	memcpy(in->buffer+in->len, str, len+1 );
	in->len += len;
	if(Debug)fprintf(stderr,
		"Save_outbuf_len: start inbuffers 0x%lx, in 0x%lx, buffer 0x%lx, len %d, max_len %d\n",
		Cast_ptr_to_long(inbuffers), Cast_ptr_to_long(in),
		Cast_ptr_to_long(in->buffer), in->len, in->max_len );
	return( in );
}

void usage(void)
{
	char *s;

	if( (s = safestrrchr( Name, '/')) ){
		Name  = s+1;
	}
	fprintf( stderr, "usage: %s [-u] [-t] [port]\n", Name );
	fprintf( stderr, "  -u = use UDP\n" );
	fprintf( stderr, "  -t = use TCP (default)\n" );
	fprintf( stderr, "  -d = Debug\n" );
	fprintf( stderr, "  port = port to use (%d default)\n", port_num  );
	exit(1);
}
	


int main(int argc, char *argv[], char *envp[] )
{
	int n, i, c, err, max_port = 0;
	char *portname, *s;
	struct servent *servent;
	struct info *in;

#if !defined(HAVE_SETLINEBUF)
# define setlinebuf(F) setvbuf(F, (char *)NULL, _IOLBF, 0)
#endif
	setlinebuf(stdout);
	Name = argv[0];
	opterr = 1;
	while( (n = getopt(argc, argv, "dut")) != EOF ){
		switch(n){
		default:  usage(); break;
		case 'u': use_udp = !use_udp; break;
		case 't': use_tcp = !use_tcp; break;
		case 'd': Debug = 1; break;
		}
	}
	i = argc - optind;
	if( i > 1 ) usage();
	if( i == 1 ){
		portname = argv[optind];
		n = atoi( portname );
		if( n <= 0 ){
			servent = getservbyname( portname, "udp" );
			if( servent ){
				n = ntohs( servent->s_port );
			}
		}
		if( n <= 0 ){
			fprintf( stderr, "udp_open_listen: bad port number '%s'\n",portname );
			usage();
		}
		port_num = n;
	}

	if( !use_tcp && !use_udp ) use_udp = 1;
	if( Debug ){
		fprintf(stderr,"monitor: udp %d, tcp %d, port %d\n",
			use_udp, use_tcp, port_num );
	}

	max_port = 0;
	FD_ZERO( &readfds );
	if( use_udp && (udp_fd = udp_open_listen( port_num )) >= 0){
		if( Debug ) fprintf(stderr,"monitor: udp port %d\n", udp_fd );
		FD_SET(udp_fd, &readfds);
		if( udp_fd >= max_port ) max_port = udp_fd+1;
	}
	if( use_tcp && (tcp_fd = tcp_open_listen( port_num )) >= 0){
		if( Debug ) fprintf(stderr,"monitor: tcp port %d\n", tcp_fd );
		FD_SET(tcp_fd, &readfds);
		if( tcp_fd >= max_port ) max_port = tcp_fd+1;
	}
	if( Debug ){
		fprintf(stderr,"monitor: max_port %d\n", max_port );
		for( i = 0; i < max_port; ++i ){
			if( FD_ISSET(i, &readfds) ){
				fprintf(stderr,"monitor: initial on %d\n", i );
			}
		}
	}


	while(1){
		FD_ZERO( &testfds );
		for( i = 0; i < max_port; ++i ){
			if( FD_ISSET(i, &readfds) ){
				if( Debug ) fprintf(stderr,"monitor: waiting on %d\n", i );
				FD_SET(i, &testfds);
			}
		}
		if( Debug ) fprintf(stderr,"monitor: starting wait, max %d\n", i );
		n = select( i,
			FD_SET_FIX((fd_set *))&testfds,
			FD_SET_FIX((fd_set *))0, FD_SET_FIX((fd_set *))0,
			(struct timeval *)0 );
		err = errno;
		if( Debug ) fprintf(stderr,"monitor: select returned %d\n", n );
		if( n < 0 ){
			fprintf( stderr, "select error - %s\n", strerror(errno) );
			if( err != EINTR ) break;
		}
		if( n > 0 ) for( i = 0; i < max_port; ++i ){
			if( FD_ISSET(i, &testfds) ){
				if( Debug ) fprintf(stderr,"monitor: input on %d\n", i );
				if( i == tcp_fd ){
					struct sockaddr_in sinaddr;
					socklen_t len = sizeof( sinaddr );
					i = accept( tcp_fd, (struct sockaddr *)&sinaddr, &len );
					if( i < 0 ){
						fprintf( stderr, "accept error - %s\n",
							strerror(errno) );
						continue;
					}
					fprintf( stdout, "connection from %s\n",
						inet_ntoa( sinaddr.sin_addr ) );
					if( i >= max_port ) max_port = i+1;
					FD_SET(i, &readfds);
				} else {
					c = read( i, buffer, sizeof(buffer)-1 );
					if( c == 0 ){
						/* closed connection */
						fprintf(stdout, "closed connection %d\n", i );
						close( i );
						FD_CLR(i, &readfds );
						Clear_buffer(i);
					} else if( c > 0 ){
						buffer[c] = 0;
						if(Debug)fprintf( stdout, "recv port %d: %s\n", i, buffer );
						Add_buffer(i);
						in = Save_outbuf_len( i, buffer, c );
						while( (s = safestrchr(in->buffer,'\n')) ){
							*s++ = 0;
							Decode(in->buffer);
							memmove(in->buffer,s, safestrlen(s)+1 );
							in->len = safestrlen(in->buffer);
						}
					} else {
						fprintf( stderr, "read error - %s\n",
							strerror(errno) );
						close( i );
						FD_CLR(i, &readfds );
					}
				}
			}
		}
	}
	return(0);
}

void UnescStr( char *s )
{
	char *t;
	int c;
	if( !ISNULL(s) ){
		for( t = s; (c = cval(s)); ++s ){
			if( c == '+' ){
				*t++ = ' ';
			} else if( c != '%' ){
				*t++ = c;
			} else {
				char val[3];
				++s;
				val[0] = *s;
				++s;
				val[1] = *s;
				val[2] = 0;
				*t++ = strtol(val,0,16);
			}
		}
		*t = 0;
	}
}

/*
 * assume we are called with a single line
 *   This has the form
 *     key=ESCESCvalue%0akey=ESCESCvalue%0a...
 */
void Decode( char *in )
{
	OBJ *hash = 0, *valuelist = 0;
	char *s, *value;
	int i;

	fprintf(stdout,"****\n");
	if( Debug ) printf( "Decode: input '%s'\n", in );
	/* next, we unescape it */
	UnescStr(in);
	fprintf(stdout, "%s\n", in );
	fflush(stdout);
}

int Set_reuse( int sock )
{
	int status = 0;
#ifdef SO_REUSEADDR
	int option = 1;
	status =  setsockopt( sock, SOL_SOCKET, SO_REUSEADDR,
			(char *)&option, sizeof(option) );
#endif
	return( status );
}

int udp_open_listen( int port )
{
	int i, fd, err;
	struct sockaddr_in sinaddr;

	sinaddr.sin_family = AF_INET;
	sinaddr.sin_addr.s_addr = INADDR_ANY;
	sinaddr.sin_port = htons( port );

	fd = socket( AF_INET, SOCK_DGRAM, 0 );
	err = errno;
	if( fd < 0 ){
		fprintf(stderr,"udp_open_listen: socket call failed - %s\n", strerror(err) );
		return( -1 );
	}
	i = -1;
	i = bind( fd, (struct sockaddr *) & sinaddr, sizeof (sinaddr) );
	err = errno;

	if( i < 0 ){
		fprintf(stderr,"udp_open_listen: bind to '%s port %d' failed - %s\n",
			inet_ntoa( sinaddr.sin_addr ), ntohs( sinaddr.sin_port ),
			strerror(errno) );
		close(fd);
		fd = -1;
	}
	if( fd == 0 ){
		fd = dup(fd);
		if( fd < 0 ){
			fprintf(stderr,"udp_open_listen: dup failed - %s\n",
				strerror(errno) );
			close(fd);
			fd = -1;
		}
	}
	return( fd );
}


int tcp_open_listen( int port )
{
	int i, fd, err;
	struct sockaddr_in sinaddr;

	sinaddr.sin_family = AF_INET;
	sinaddr.sin_addr.s_addr = INADDR_ANY;
	sinaddr.sin_port = htons( port );

	fd = socket( AF_INET, SOCK_STREAM, 0 );
	err = errno;
	if( fd < 0 ){
		fprintf(stderr,"tcp_open: socket call failed - %s\n", strerror(err) );
		return( -1 );
	}
	i = Set_reuse( fd );
	if( i >= 0 ) i = bind( fd, (struct sockaddr *) & sinaddr, sizeof (sinaddr) );
	if( i >= 0 ) i = listen( fd, 10 );
	err = errno;

	if( i < 0 ){
		fprintf(stderr,"tcp_open: connect to '%s port %d' failed - %s\n",
			inet_ntoa( sinaddr.sin_addr ), ntohs( sinaddr.sin_port ),
			strerror(errno) );
		close(fd);
		fd = -1;
	}
	if( fd == 0 ){
		fd = dup(fd);
		if( fd < 0 ){
			fprintf(stderr,"tcp_open: dup failed - %s\n",
				strerror(errno) );
			close(fd);
			fd = -1;
		}
	}
	return( fd );
}

void cleanup( int n )
{
	exit( Errorcode );
}
