/*  k9linux
 *
 *   k9linux is a simple NTP broadcast client
 *
 *    (C) Copyright H.C.Mingham-Smith Ltd. 1999.  All rights reserved.
 *
 *    You have a royalty-free right to use, modify, reproduce and
 *    distribute the Sample Files (and/or any modified version) in
 *    any way you find useful, provided that you agree that
 *    H.C.Mingham-Smith Ltd. has no warranty obligations or liability for any
 *    Executable Files generated from this source code
 *
 *	Implements a basic NTP broadcast listener
 *
 *	Command line options are
 *
 *	-d	Run as debug (don't run in background, don't set the time, show trace
 *	-o	Run until time received then die
 *	-b	Beep when message received
 *
 *	Build this source as follows
 *
 *	cc k9linux.c -o k9linux
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<errno.h>
#include	<string.h>
#include	<stdarg.h>
#include	<unistd.h>
#include	<netdb.h>
#include	<netinet/in.h>
#include	<arpa/inet.h>
#include	<sys/types.h>
#include	<sys/socket.h>
#include	<sys/time.h>

#define	TRUE	1
#define	FALSE	0

/*	NTP packet (no authentication)	*/

struct ntppkt {
	unsigned char	flags[4];
	unsigned long	rootdelay;
	unsigned long	rootdispersion;
	unsigned long	refid;
	unsigned long	reftimehi;
	unsigned long	reftimelo;
	unsigned long	orghi;
	unsigned long	orglo;
	unsigned long	rechi;
	unsigned long	reclo;
	unsigned long	xmthi;
	unsigned long	xmtlo;
};

/*	Last server, stratum, time used	*/

struct last {
	int		stratum;
	unsigned long	addr;
	time_t		time;
} last;



int main(int argc, char ** argv)
{
	struct 	sockaddr_in	srv_addr;
	struct	servent		*sp;
	struct	sockaddr_in	remoteAddr;
	struct 	ntppkt		ipkt;

	int   	remoteAddrLength = sizeof(remoteAddr);
	int	mastersd;
	int	i;

	char	debug=FALSE;
	char	runonce=FALSE;
	char	beep=FALSE;

	while( (i=getopt(argc,argv,"dbo")) != EOF) {
		switch(i) {
		case 'd'	:
			debug = TRUE;
			break;

		case 'o'	:
			runonce = TRUE;
			break;

		case 'b'	:
			beep = TRUE;
			break;

		default		:
			printf("Usage: k9linux [-b][-d][-o]\n");
			exit(1);
		}
	}


	if( !debug ) {
		if( fork() ) {		/* Put process into background */
			exit(0);
		}

		setpgrp();		/* Detach from tty	*/
	}else {
		printf( "K9 V1.2 © H.C. Mingham-Smith Ltd. 1998-2000\n" );
	}

	if ((mastersd=socket(PF_INET,SOCK_DGRAM,0))== -1){
		perror("Couldn't create socket");
		exit(1);
	}

	srv_addr.sin_family		= AF_INET;
	srv_addr.sin_addr.s_addr	= INADDR_ANY;
	srv_addr.sin_port		= ((sp = getservbyname("ntp", "udp")) == 0) ? htons(123) : sp->s_port;

	if (bind(mastersd, (struct sockaddr *) &srv_addr,sizeof(srv_addr))== -1){
		perror( "Couldn't bind socket");
		exit(1);
	}

	while(1) {
		if ( recvfrom( mastersd, (char *)&ipkt, sizeof(ipkt),0, (struct sockaddr *) &remoteAddr, &remoteAddrLength ) != -1  ) {
			if( (ipkt.flags[0] & 7) == 5 ) {	/*	If it is a broadcast */
				unsigned long	secs= ntohl(ipkt.xmthi)-2208988800.0;  //Seconds since 1900
				unsigned long	subsecs= ntohl(ipkt.xmtlo);
				int 		stratum = ipkt.flags[1];

				if( debug ) {
					printf( "Time broadcast received from %s\n", inet_ntoa(remoteAddr.sin_addr) );
					printf( "Ver: %d Stratum: %d Mode: %d Poll: %d Precision %d\n",
						((ipkt.flags[0] & 0x38) >> 3), ipkt.flags[1],
						(ipkt.flags[0] & 0x07), ipkt.flags[2],
						(char)ipkt.flags[3]);
					printf("UTC Time is %s", asctime(gmtime(&secs)));

				} else {
					/*
					**	If stratum is valid and (message is from our selected server
					**	or the stratum is higher than our server or too long since a messge received
					*/

					if( ( (stratum>0) && (stratum<=16) ) && (  (last.addr==remoteAddr.sin_addr.s_addr)
						|| (last.stratum>stratum) || (abs(secs-last.time)>600) ) ) {

						struct timeval tv;
						tv.tv_sec =secs;
						tv.tv_usec=(unsigned long) 1000000*((double)subsecs/(double)0x100000000);

						if( settimeofday(&tv, NULL) == -1 ) {
							perror("Couldn't set the time");
						}

						last.addr = remoteAddr.sin_addr.s_addr;
						last.time = secs;
						last.stratum = stratum;
					}
				}

				if( beep ) {		/* Beep when we get a message */
					write(1,"\a",1);
				}

				if( runonce ) {		/* exit when we get a message */
					break;
				}
			}
		}
	}

	return (0);
}

