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

 static char *const _id =
"$Id: monitor.c,v 1.74 2004/09/24 20:19:58 papowell Exp $";


#include "lp.h"
#include "linelist.h"
#include "getopt.h"
#include "linksupport.h"
#include "getqueue.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.
 */


extern int errno;

int udp_open( int port );
int tcp_open( 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 debug;

char *prog = "???";

/*****************************************************************
 * 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( prog, '/')) ){
		prog  = s+1;
	}
	FPRINTF( STDERR, "usage: %s [-u] [-t] [port]\n", prog );
	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[] )
{
	int n, i, c, err, max_port = 0;
	char *portname, *s;
	struct servent *servent;
	struct info *in;

	prog = 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: 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( 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( 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", Errormsg(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 ){
#if defined(HAVE_SOCKLEN_T)
					socklen_t len;
#else
					int len;
#endif
					struct sockaddr_in sinaddr;
					len = sizeof( sinaddr );
					i = accept( tcp_fd, (struct sockaddr *)&sinaddr, &len );
					if( i < 0 ){
						FPRINTF( STDERR, "accept error - %s\n",
							Errormsg(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 = ok_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",
							Errormsg(errno) );
						close( i );
						FD_CLR(i, &readfds );
					}
				}
			}
		}
	}
	return(0);
}

void Decode( char *in )
{
	struct line_list l, cf, info;
	char *s, *t, *header, *value;
	int i;

	Init_line_list(&l);
	Init_line_list(&cf);
	Init_line_list(&info);

	FPRINTF(STDOUT,"****\n");
	if( debug )FPRINTF(STDOUT, "Decode: %s\n", in );
	if((s = safestrpbrk(in,Hash_value_sep)) ){
		*s++ = 0;
		Unescape(s);
		Free_line_list(&l);
		Split(&l,s,Line_ends,1,Hash_value_sep,1,1,1,0);
		for( i = 0; i < l.count; ++i ){
			t = l.list[i];
			if( debug || safestrncasecmp(t,VALUE,5) ){
				FPRINTF(STDOUT,"%s\n", t );
			}
		}
		s = Find_str_value(&l,VALUE);
		if( s ) Unescape(s);
		if( !safestrcasecmp(in,TRACE) ){
			FPRINTF(STDOUT,"TRACE: '%s'\n", s );
		} else if( !safestrcasecmp(in,UPDATE) ){
			FPRINTF(STDOUT,"UPDATE: '%s'\n", s );
			Split(&info,s,Line_ends,0,0,0,0,0,0);
			for( i = 0; i < info.count; ++i ){
				header = info.list[i];
				if( (value = safestrchr(header,'=')) ) *value++ = 0;
				Unescape(value);
				FPRINTF(STDOUT," [%d] %s='%s'\n", i, header, value );
			}
		} else if( !safestrcasecmp(in,PRSTATUS) ){
			FPRINTF(STDOUT,"PRSTATUS: '%s'\n", s );
		} else if( !safestrcasecmp(in,QUEUE) ){
			FPRINTF(STDOUT,"QUEUE: '%s'\n", s );
			Split(&info,s,Line_ends,0,0,0,0,0,0);
			for( i = 0; i < info.count; ++i ){
				header = info.list[i];
				if( (value = safestrchr(header,'=')) ) *value++ = 0;
				Unescape(value);
				FPRINTF(STDOUT," [%d] %s='%s'\n", i, header, value );
			}
			Free_line_list(&info);
		} else if( !safestrcasecmp(in,DUMP) ){
			FPRINTF(STDOUT,"DUMP:\n");
			Split(&info,s,Line_ends,0,0,0,0,0,0);
			for( i = 0; i < info.count; ++i ){
				header = info.list[i];
				if( (value = safestrchr(header,'=')) ) *value++ = 0;
				Unescape(value);
				if(debug) FPRINTF(STDOUT," [%d] %s='%s'\n", i, header, value );
				if( !safestrcasecmp(header,QUEUE) ){
					FPRINTF(STDOUT," EXTRACT QUEUE '%s'\n",value);
				} else if( !safestrcasecmp(header,UPDATE) ){
					FPRINTF(STDOUT," EXTRACT UPDATE '%s'\n",value);
				} else {
					FPRINTF(STDOUT," EXTRACT '%s' '%s'\n",header, value);
				}
			}
			Free_line_list(&info);
		} else {
			FPRINTF(STDOUT,"%s: '%s'\n", in, s );
		}
	}
	Free_line_list(&l);
	Free_line_list(&cf);
	Free_line_list(&info);
	FPRINTF(STDOUT,"\n");
}
int udp_open( 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 );
	Max_open(fd);
	err = errno;
	if( fd < 0 ){
		FPRINTF(STDERR,"udp_open: socket call failed - %s\n", Errormsg(err) );
		return( -1 );
	}
	i = -1;
	i = bind( fd, (struct sockaddr *) & sinaddr, sizeof (sinaddr) );
	err = errno;

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


int tcp_open( 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 );
	Max_open(fd);
	err = errno;
	if( fd < 0 ){
		FPRINTF(STDERR,"tcp_open: socket call failed - %s\n", Errormsg(err) );
		return( -1 );
	}
	i = Link_setreuse( 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 ),
			Errormsg(errno) );
		close(fd);
		fd = -1;
	}
	if( fd == 0 ){
		fd = dup(fd);
		if( fd < 0 ){
			FPRINTF(STDERR,"tcp_open: dup failed - %s\n",
				Errormsg(errno) );
			close(fd);
			fd = -1;
		}
	}
	return( fd );
}
