//
// @(#) $Header: /opt/ntp/timelord/shm/RCS/ntpscon.c,v 2.0.0.18 2006/06/16 00:49:39 skia Exp $
// @(#) NTP ONCORE driver, shared memory to socket connector xaos@darksmile.net 1/06/2002
// @(#) Update to allow shared memory messages for GT, UT and M12 receivers
// @(#) Update to allow a maximum connection limit
//
// @(#) This software is relased under the GPL and
// @(#) is Copyright (C)GS3 Technologies Inc. 2000-2006
// @(#) For updates check: http://www.gs3tech.com/
//
#include "ntpscon.h"
//
// Start the connector
//
int main(argc, argv, envp)
	int argc;
	char *argv[];
	char *envp[];
{
	char *file;
	struct sigaction act, oldact;
	int i, j, size;
	//
	int sem_set_id;					// ID of the semaphore set
	union semun sem_val;			// semaphore value, for semctl()
	struct sembuf sem_op;			// structure for semaphore ops.
	//
	// Save our name
	//
	myname = argv[0];

	//
	// Process args
	//
	if( parsearg(argc, argv) ) exit(1); 
	//
	if( debug ) {
		printf("%s: Debug level: %d, Verbose level: %d, Maximum Connections: %d, Port: %s\n", myname, debug, verbose, maxconn, portl);
	}
	//
	// See if we can create a semaphore
	sem_set_id = semget(IPC_PRIVATE, 1, (IPC_CREAT | IPC_EXCL | 0600));
	if (sem_set_id == -1) {
		fprintf(stderr,"%s: Unable to create a semaphore\r\n", myname);
		exit(EXIT_FAILURE);
	} else {
		if( debug ) {
			printf("Semaphore set created, semaphore set id '%d'.\n", sem_set_id);
		}
		// Intialize the first (and single) semaphore in our set to maxconn
		sem_val.val = maxconn;
		j = semctl(sem_set_id, 0, SETVAL, sem_val);
		if( j ) {
			fprintf(stderr,"%s: Unable to initialize the semaphore. Error: %d\r\n", myname, errno);
			exit(EXIT_FAILURE);
		}
	}
	//
	// Find the ONCORE Shared memory fron the configuration file
	//
	file = proconf();

	//
	// Signal handling
	//
	ignore_pipe();				// Ignore SIGPIPE
	act.sa_handler = SIG_IGN;
    act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGHUP, &act, NULL);

	//
	// Prepare for CHILD stopped or terminated
	//
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_handler = sig_chld;
	sigaction(SIGCHLD, &act, &oldact);
	port = atoport(portl, "tcp");
	if( port == -1 ) {
		fprintf(stderr,"%s: Unable to find service: %s\n", myname, argv[1]);
		exit(EXIT_FAILURE);
	}

	//
	// Put a message in syslog stating the time that we started
	//
	openlog( myname, LOG_CONS, LOG_USER );
	syslog(LOG_NOTICE, "%s\n", "ntpscon started" );
	closelog();

	//
	// Maybe we need to run as a daemon
	//
	if( forkit ) {
		if( daemon(0,0) < 0 ) exit(2);
	}

	//
	// This is tricky. This function will never return
	// when this program is called initially.
	// It will return to a child process which can then
	// process the request.
	//
	sock = get_connection(SOCK_STREAM, port, NULL);
	//
	// I am now a child. Put a log msg and start the connection.
	// 
	i=sizeof(soname);
	openlog( myname, LOG_CONS, LOG_USER );
		if( getpeername( sock, (struct sockaddr *)&soname, &i) < 0 ) {
			syslog(LOG_NOTICE, "\r\n", "Error getting connection peername");
			if( debug ) {
				printf("Error getting connection peername\r\n");
			}
		}
		else {
			if( debug ) {
				printf("Accepted connection from IP: %s, port: %d\n", inet_ntoa(soname.sin_addr), (int)ntohs(soname.sin_port));
			}
			syslog(LOG_NOTICE, "Accepted connection from IP: %s, port: %d\n", inet_ntoa(soname.sin_addr), (int)ntohs(soname.sin_port));
		}
		//
		// Check if we have enough slots open
		j = semctl(sem_set_id, 0, GETVAL, sem_val);
		if( j != -1 ) {
			j = (maxconn-j+1);
			if( j <= maxconn ) {
				if( debug ) {
					printf("Connection number: %d out of maximum: %d\n", j, maxconn);
				}
				syslog(LOG_NOTICE, "Connection number: %d out of maximum: %d\n", j, maxconn);
				// Decrease the value of the semaphore by 1
				sem_op.sem_num = 0;
				sem_op.sem_op = -1;
				sem_op.sem_flg = 0;
				j = semop(sem_set_id, &sem_op, 1);
				if( j ) {
					fprintf(stderr,"%s: Unable to decrement semaphore. Error: %d\r\n", myname, errno);
					closelog();
					close(sock);
					sleep(1);
					return 0;
				}
			} else {
				if( debug ) {
					printf("Connection maximum of: %d reached. Connection dropped.\n", maxconn);
				}
				syslog(LOG_NOTICE, "Connection maximum of: %d reached. Connection dropped.\n", maxconn);
				closelog();
				close(sock);
				sleep(1);
				return 0;
			}
		} else {
			fprintf(stderr,"%s: Unable to get value of semaphore. Error: %d\r\n", myname, errno);
			closelog();
			close(sock);
			sleep(1);
			return 0;
		}
	closelog();
	//
	// Prepare to increase the value of the semaphore by 1
	sem_op.sem_num = 0;
	sem_op.sem_op = 1;
	sem_op.sem_flg = 0;
	//
	if ((fd=open(file, O_RDONLY)) < 0) {
		fprintf(stderr, "%s[%d] Cant open %s\n", myname, getpid(), file);
		close(sock);
		//
		// Now, increment the semaphore
		j = semop(sem_set_id, &sem_op, 1);
		if( j ) {
			fprintf(stderr,"%s: Unable to increment semaphore. Error: %d\r\n", myname, errno);
		}
		sleep(1);
		exit(1);
	}

	if (stat(file, &statbuf) < 0) {
		fprintf(stderr, "%s[%d] Can't stat %s\n", myname, getpid(), file);
		close(sock);
		//
		// Now, increment the semaphore
		j = semop(sem_set_id, &sem_op, 1);
		if( j ) {
			fprintf(stderr,"%s: Unable to increment semaphore. Error: %d\r\n", myname, errno);
		}
		sleep(1);
		exit(1);
	}

	size = statbuf.st_size;
	if ((Buf=mmap(0, size, PROT_READ, MAP_SHARED, fd, (off_t) 0)) < 0) {
		fprintf(stderr, "%s[%d] MMAP failed\n", myname, getpid());
		close(sock);
		//
		// Now, increment the semaphore
		j = semop(sem_set_id, &sem_op, 1);
		if( j ) {
			fprintf(stderr,"%s: Unable to increment semaphore. Error: %d\r\n", myname, errno);
		}
		sleep(1);
		exit(1);
	}

	//
	// Read from ONCORE shared memory and push to client
	//
	for(;;) {
		if( read_data() < 0 ) break;
		sleep(1);
	}
	if( debug ) printf("%s: Lost connection.\n", myname);
	//
	// Now, increment the semaphore
	j = semop(sem_set_id, &sem_op, 1);
	if( j ) {
		fprintf(stderr,"%s: Unable to increment semaphore. Error: %d\r\n", myname, errno);
	}
	close(sock);
	sleep(1);
	return 0;
}
//
// Read from the mmaped file and process each line
//
int read_data()
{
	int i, j, k, n, iseq, jseq;
	uchar *cp, *cp1;
	int sockstat;

	for(cp=Buf+1; (n = 256*(*cp) + *(cp+1)) != 0;  cp+=(n+3)) {
		//
		// Now we check against the number of messages we process
		//
		for (k=0; k < sizeof(Hdr)/sizeof(Hdr[0]);  k++) {
			if (!strncmp(cp+3, Hdr[k].c, 4)) {	// Am I interested?
				iseq = *(cp+2);
				jseq = Hdr[k].seq;
				Hdr[k].seq = iseq;
				if (iseq > jseq) {				// Has it changed?
					// verify checksum
					j = 0;
					cp1 = cp+3;					// Points to start of oncore response
					for (i=2; i < n-3; i++)
						j ^= cp1[i];
					if (j == cp1[n-3]) {		// Good checksum
						//
						// Send to the client
						//
						sockstat = sock_write( sock, &cp[3], n );
						if( debug )
							printf("%s: Send to client: [%3d]%c%c%c%c - [%3d]\n", myname, n, cp[3], cp[4], cp[5], cp[6], sockstat);
						if( sockstat < 0 ) return sockstat;
					}
					else {
						fprintf(stderr, "%s: Bad Checksum for %s\n", myname, Hdr[k].c);
						break;
					}
				}
			}
		}
		//
		// These are treated differently since the shared memory
		// for these commands has multiple slots.
		if (!strncmp(cp+3, "@@Ea", 4))
			cp += 3*(n+3);
		if (!strncmp(cp+3, "@@Ba", 4))
			cp += 3*(n+3);
		if (!strncmp(cp+3, "@@Ha", 4))
			cp += 3*(n+3);
		if (!strncmp(cp+3, "@@Cb", 4))
			cp += 34*(n+3);
	}
	return sockstat;
}

//
// This waits for all children, so that they don't become zombies.
//
void sig_chld(signal_type)
	int signal_type;
{
	int pid;
	int status;

	//
	// Now, wait for the child to go away
	//
	while ( (pid = wait3(&status, WNOHANG, NULL)) > 0);
}

//
// Process configuration file looking for ONCORE shared mamory
// 
char *proconf()
{
	char *cp, *cc, *ca, line[100];
	int i;
	FILE *xfd;
	char *file;

	if ((xfd=fopen(CFILE1, "r")) == NULL ) {
		if ((xfd=fopen(CFILE2, "r")) == NULL ) {
			if ((xfd=fopen(CFILE3, "r")) == NULL ) {
				fprintf(stderr, "%s: Can't open any oncore configuration files.\n", myname);
				exit(1);
			}
		}
	}
	
	//
	// Thanx to <phk@FreeBSD.ORG> Poul-Henning Kamp for the file parsing code
	//
	while (fgets(line, 100, xfd)) {
		// Remove comments
		if ((cp = strchr(line, '#'))) *cp = '\0'; 

		// Remove trailing space
		for (i = strlen(line); i > 0 && isascii((int)line[i - 1]) && isspace((int)line[i - 1]);)
		line[--i] = '\0'; 

		// Remove leading space
		for (cc = line; *cc && isascii((int)*cc) && isspace((int)*cc); cc++) continue;

		// Stop if nothing left
		if (!*cc) continue;

		// Uppercase the command and find the arg
		for (ca = cc; *ca; ca++) { 
			if (isascii((int)*ca)) {
				if (islower((int)*ca)) {
					*ca = toupper(*ca);
				} else if (isspace((int)*ca) || (*ca == '='))
					break;
			}
		}
		// Remove space (and possible =) leading the arg
		for (; *ca && isascii((int)*ca) && (isspace((int)*ca) || (*ca == '=')); ca++)
		continue;

		if (!strncmp(cc, "STATUS", (size_t) 6) || !strncmp(cc, "SHMEM", (size_t) 5)) {
			i = strlen(ca);
			file = (char *) malloc((unsigned) (i+1));
			strcpy(file, ca);
			break;					// We found it!
		}
	}
	fclose(xfd);
	return file;
}

//
// process arguments
//
int parsearg( argc, argv )
	int argc;
	char **argv;
{
	int c;
	int digit_optind = 0;
	int option_index = 0;
	int retcode=0;
	static struct option long_options[] = {
		{"port", 1, 0, 'p'},
		{"debug", 0, 0, 'd'},
		{"verbose", 0, 0, 'v'},
		{"maxconn", 0, 0, 'm'},
		{"nofork", 0, 0, 'n'},
		{"help", 0, 0, 'h'},
		{0, 0, 0, 0}
	};

	opterr=0;

	while(1) {
		c = getopt_long( argc, argv, "p:dvm:nh", long_options, &option_index );
		if(c == -1) break;

		switch (c) {
			case 'p':
				if (optarg) portl=optarg; 
				break;

			case 'd':
				debug++;
				forkit=0;
				break;

			case 'v':
				verbose++;
				forkit=0;
				break;

			case 'm':
				if (optarg) {
					maxconn=atoi(optarg); 
				}
				break;

			case 'n':
				forkit=0;
				break;

			case 'h':
			case '?':
				retcode++;
				break;

			default:
				break;
		}
	}

	if( (optind < argc) || retcode ) {
		retcode++;
		printf("usage: %s [-h|-?|--help] [-v|--verbose] [-d|--debug] [-m|--maxconn nnn] [-n|--nofork] [ [-p|--port port]\n", argv[0]);
	}
	return(retcode);
}
