/*
 * $Id: in.gate.c,v 1.3 91/12/23 11:14:55 pochmara Exp $
 *
 * in.gate.c
 *
 *  John Pochmara (pochmara@cse.ogi.edu)
 *  Oregon Graduate Institute of Science & Technology
 *
 *
 * Copyright (c) 1991, John Pochmara, pochmara@cse.ogi.edu
 *
 * Permission is granted to copy and distribute this file in modified
 * or unmodified form, for noncommercial use, provided (a) this copyright
 * notice is preserved, (b) no attempt is made to restrict redistribution
 * of this file, and (c) this file is not distributed as part of any
 * collection whose redistribution is restricted by a compilation copyright.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>

#include <stdio.h>
#include <syslog.h>
#include <strings.h>

static void fatal();
static int ReadLine();
static int ServiceAccess();
static int CheckAccess();
static int ConvertHostAddr();
static int CompareHost();
static int CheckTime();
static int OkTime();
static int OkMinute();

#ifndef CONFIG_FILE
#define CONFIG_FILE	"/etc/in.gate.conf"
#endif

#define TRUE		(1)
#define FALSE		(0)

#define BIGBUF		(2048)
#define SMALLBUF	(512)

#define EXACT		(0)
#define META		(1)
#define NOMATCH		(2)

#define WILD_CARD	(-2)

struct ip_addr {
	int ha_oct[4];
};

static char *Version = "1.0 patchlevel 0";

main( argc, argv )
int argc;
char *argv[];
{
	struct sockaddr_in from;
	char *hostdot;
	char *service;
	char *path;
	char buf[BIGBUF];
	int fromlen;
	int datagram = FALSE;

#ifdef LOG_DAEMON
	openlog( "in.gate", LOG_PID, LOG_AUTH );
#else
	openlog( "in.gate", LOG_PID );
#endif

#ifdef DEBUG
	syslog( LOG_DEBUG, "Hello from in.gate %s", Version ); 
	syslog( LOG_INFO, "Checking access for service '%s'", argv[1] );
#endif

	fromlen = sizeof( from );
	if( getpeername( 0, &from, &fromlen ) < 0 ) {

		/* 
		 * maybe its a datagram... 
		 */
		fromlen = sizeof( from );
		if( recvfrom( 0, buf, sizeof(buf), 
					MSG_PEEK, &from, &fromlen ) < 0 ) {
			syslog( LOG_NOTICE, "could not find source host!? (%m)" );
			exit( 1 );
		}
		datagram = TRUE;
	}

	hostdot = inet_ntoa( from.sin_addr );
	service = argv[1];

#ifdef DEBUG
	syslog( LOG_DEBUG, "Checking Host: %s\nService: %s\n", 
							hostdot, service );
#endif DEBUG

	if( ServiceAccess( &from, service ) != TRUE ) {
		syslog( LOG_WARNING, "service denied to %s for %s",
							hostdot, service );
		closelog();
	
		if( datagram == FALSE ) {
			fatal( 0, "Access denied" );
		} else {
			/* consume packet */
			recvfrom( 0, buf, sizeof(buf), 0, &from, &fromlen );
			exit( 0 );
		}
	} else {
		syslog( LOG_INFO, "service granted to %s for %s", 
							hostdot, service );
	}

	closelog();

	path = argv[0];
	argv++;

	execv( path, argv );

#ifdef LOG_DAEMON
	openlog( "in.gate", LOG_PID, LOG_DAEMON );
#else
	openlog( "in.gate", LOG_PID );
#endif 
	syslog( LOG_NOTICE, "could not execv '%s'", path );

	closelog();

	fatal( 0, "service fialed" );
}

static void fatal( fd, msg )
int fd;
char *msg;
{
	char tmp[BUFSIZ];

	sprintf( tmp, "\r\n %s \r\n\r\n", msg );
	write( fd, tmp, strlen( tmp ) );

	exit( 0 );
}

/*
 * ServiceAccess( host, service )
 *
 *
 * parameters:
 *	host - sockaddr_in for the host to check
 *	service - service that people wish to use.
 *
 * returns:
 *	TRUE ( 1 ) - if the service is usable by host.
 *	FALSE ( 0 ) - if the host can not use the the service.
 *
 */
static int ServiceAccess( host, service )
struct sockaddr_in *host;
char *service;
{
	FILE *fp;
	int ret;


	if( ( fp = fopen( CONFIG_FILE, "r" ) ) == NULL ) {
		/* access file missing, return TRUE then */
		syslog( LOG_WARNING, "could not open '%s', defaulting to allowing access", CONFIG_FILE );
		return( TRUE );
	}

	
	ret = CheckAccess( fp, host, service );

	fclose( fp );

#ifdef DEBUG
	syslog( LOG_DEBUG, "%s %s use %s.", host, ret ? "can" : "can not", 
								service );
#endif DEBUG

	return( ret );
}

/*
 *
 *
 */
static int CheckAccess( fp, host, service )
FILE *fp;
struct sockaddr_in *host;
char *service;
{
	struct ip_addr host_pat;
	struct ip_addr src_host;
	char f_service[SMALLBUF];
	char f_use[SMALLBUF];
	char f_times[BIGBUF];

	ConvertHostAddr( host, &src_host );

	while( ReadLine( fp, &host_pat, f_service, f_use, f_times ) == TRUE ) {

#ifdef DEBUG
	if( f_times[0] == NULL ) {
		syslog( LOG_DEBUG, "Read in: %d.%d.%d.%d %s %s\n", 
				host_pat.ha_oct[0], host_pat.ha_oct[1], 
				host_pat.ha_oct[2], host_pat.ha_oct[3] , 
				f_service, f_use );
	} else {
		syslog( LOG_DEBUG, "Read in: %d.%d.%d.%d %s %s %s\n", 
				host_pat.ha_oct[0], host_pat.ha_oct[1], 
				host_pat.ha_oct[2], host_pat.ha_oct[3] , 
				f_service, f_use, f_times );
	}
#endif DEBUG

		if( ( CompareHost( &host_pat, &src_host ) != NOMATCH ) &&
		    ( strcmp( f_service, service ) == 0 ) ) {

#ifdef DEBUG
			syslog( LOG_DEBUG, "Host and server matched." );
#endif DEBUG

			switch( f_use[0] ) {

				case 'Y':
				case 'y':
					if( f_times[0] != NULL ) {
#ifdef DEBUG
						syslog( LOG_DEBUG, 
							"checking times..." );
#endif DEBUG
						return( CheckTime( f_times ) );
					} else {
						return( TRUE );
					}
					break;
				default:
					return( FALSE );
			}

		}

	}

	/* host was not listed in access file,  what to do.... */

	return( TRUE );

}

/*
 * ConvertHostAddr( shost, ahost )
 *
 *  Converts the sin_addr form of the host address to an internal form.
 *
 * parameters:
 *	shost - sin_addr form of address.
 *	ahost - a pointer to the structure ip_addr.
 *
 * returns:
 *	TRUE - if convertion went ok.
 *	FALSE - if conversion fialed.
 *
 */
static int ConvertHostAddr( shost, ahost )
struct sockaddr_in *shost;
struct ip_addr *ahost;
{
	int n;
	char *c;

	c = (char *)&(shost->sin_addr.s_addr);

	ahost->ha_oct[0] = (int)(c[0] & 0xff);
	ahost->ha_oct[1] = (int)(c[1] & 0xff);
	ahost->ha_oct[2] = (int)(c[2] & 0xff);
	ahost->ha_oct[3] = (int)(c[3] & 0xff);

	return( TRUE );
}

/*
 * Comparehost( patern, host )
 *
 *  Compares host to patern.
 *
 * parameters:
 *	patern - patern to compare agaist.
 *	host - host address to  compare.
 *
 * returns:
 *	EXACT - if host == patern
 *	META - if host matchs patern due to META chars.
 *	NOMATCH - if host does not match patern.
 *
 */ 
static int CompareHost( patern, host )
struct ip_addr *patern;
struct ip_addr *host;
{


	if( ( patern->ha_oct[0] == host->ha_oct[0] ) &&
	    ( patern->ha_oct[1] == host->ha_oct[1] ) &&
	    ( patern->ha_oct[2] == host->ha_oct[2] ) &&
	    ( patern->ha_oct[3] == host->ha_oct[3] ) ) {

		return( EXACT );

	} else if( ( ( patern->ha_oct[0] == host->ha_oct[0] ) || 
		     ( patern->ha_oct[0] == WILD_CARD ) ) &&
		   ( ( patern->ha_oct[1] == host->ha_oct[1] ) ||
		     ( patern->ha_oct[1] == WILD_CARD ) ) &&
		   ( ( patern->ha_oct[2] == host->ha_oct[2] ) ||
		     ( patern->ha_oct[2] == WILD_CARD ) ) &&
		   ( ( patern->ha_oct[3] == host->ha_oct[3] ) ||
		     ( patern->ha_oct[3] == WILD_CARD ) ) ) {

			return( META );

	} else { 
	
		return( NOMATCH );

	}
}


static int CheckTime( times )
char *times;
{
	char *tmp,*pl;
	int s_min, s_hour;
	int e_min, e_hour;
	struct tm *now;
	int n;
	long clock;


	clock = time( (long *) 0 );
	now = localtime( &clock );

	pl = tmp = times;

	while( pl != NULL ) {

		tmp = index( times, ',' );

		if( tmp != NULL ) {
			*tmp = NULL;
			tmp++;
		}

		n = sscanf( pl, "%d:%d-%d:%d", &s_hour, &s_min, 
							&e_hour, &e_min );

		if( n == 4 ) {

			if( OkTime(now,s_hour,s_min,e_hour,e_min) == TRUE ) {
				return( TRUE );
			}

		}

		pl = tmp;

	}

	return( FALSE );

}

static int OkTime( now, sh, sm, eh, em )
struct tm *now;
int sh, sm;
int eh, em;
{
	if( eh < sh ) {

		if( ( ( now->tm_hour >= sh ) || ( now->tm_hour <= eh ) ) &&
		     ( OkMinute( now, sm, em ) == TRUE ) ) {
			return( TRUE );
		} else {
			return( FALSE );
		}

	} else {

		if( ( ( now->tm_hour >= sh ) && ( now->tm_hour <= eh ) ) &&
		    ( OkMinute( now, sm, em ) == TRUE ) ) {
			return( TRUE );
		} else {
			return( FALSE );
		}

	}
}

static int OkMinute( now, sm, em ) 
struct tm *now;
int sm, em;
{
	if( ( now->tm_min >= sm ) || ( now->tm_min <= em ) ) {
		return( TRUE );
	} else {
		return( FALSE );
	}
}

/*
 * ReadLine( fp, host, service, use, times )
 *
 *
 * parameters:
 *	fp - File to read from.
 *	host - filled in with the host addr
 *	service - filled in with service name
 *	use - filled in with use field
 *	times - filled in with times field
 *
 * returns: 
 *	TRUE - if got a line.
 *	FALSE - if did not get a line, I.E. EOF
 *
 */
static int ReadLine( fp, host, service, use, times )
FILE *fp;
struct ip_addr *host;
char *service;
char *use;
char *times;
{
	extern int atoi();
	char buf[BIGBUF];
	char sh[SMALLBUF];
	char *tmp1, *tmp2;
	int n,i;

	while( fgets( buf, BIGBUF, fp ) != NULL ) {


		if( buf[0] == '#' ) {	/* skip comment lines */
			continue;
		}

		n = sscanf( buf, "%s %s %s %s", sh, service, use, times );
		switch( n ) {

			case EOF:
			case 0:
			case 1:		/* skip bad lines */
			case 2:
				continue;	
				break;
			case 3:
				times[0] = NULL;
			case 4:
				tmp2 = sh;
				for( i = 0; i < 4; i++ ) {

				  tmp1 = index( tmp2, '.' );
				  if( tmp1 == NULL && i != 3 ) break;
				  if( tmp1 != NULL ) {
					 *tmp1 = NULL;
					  tmp1++;
				  }
				  if( *tmp2 == '*' ) {
					host->ha_oct[i] = WILD_CARD;
				  } else {
					host->ha_oct[i] = atoi( tmp2 );
				  }
				  tmp2 = tmp1;
				}

				return( TRUE );
				break;
		}

	}

	return( FALSE );

}
