/*
* synclocks [+/-options] authority...
*
* This program queries the clocks on the authority host(s),  and  uses
* the  adjtime()  system call to adjust the local clock to the mean of
* the hosts' clocks.   If  one  or  more  of  the  named  hosts  isn't
* reachable, we ignore it and use the remaining hosts.
*
* If this program is run on the local servers,  their  clocks  can  be
* kept  synchronized;  other  machines can then use the server(s) as a
* time standard.
*
* This  program  is a lot more efficient than Sun's tool, but it isn't
* really good for better than the nearest second.  This  suffices  for
* editing and make.
*
* Some options:
*
* -a
* +a
*   Do the adjustment.  Default: +a.  Will fail if not super-user.
*
* -f
* +f<n>
*   Repeat forever. The -f and -r option suppress this; only one syncs
*   will be done, and then we exit.  The +f and +r option mean to keep
*   running and repeat the operation every <n> seconds.  Default:  -f,
*   which  will  bring  the  clock into sync and exit, after which the
*   local clock may slowly drift..
*
* -i
* +i<n>
*   Interval between repeats, in seconds.  The "-i" option  suppresses
*   repeats,  and is equivalent to "-f".  The default is "-i".  If you
*   use +i, <n> should probably be several hours, unless your clock is
*   particularly bad.
*
* -r
* +r<n>
*   Use the rtime() routine to query the authorities. If <n> is given,
*   it is the interval between queries.  The default is "-r".  This is
*   not used by default, because few systems seem to have this service
*   running.   If  it is common at your site, you might want to change
*   the default to use this rather than +t.
*
* -t
* +t<n>
*   Use the UDP time service to query the authorities.  <n> is the UDP
*   port to use; the default is "+t37".
*
* The default behavior with only a list of authority hostnames  is  to
* use  the  UDP  time  service  just  once,  wait  up to 5 seconds for
* replies, and then use the mean time in the  replies  to  adjust  the
* system  clock.  The result will be clocks that are synchronized well
* enough (within a second) for the purpose of things like make(1), but
* not nearly well enough for, say, long-baseline interferometry.
*
* Experimenting has shown that there are hosts  on  the  network  that
* return  a  Unix  timestamp  (seconds since the start of 1970) rather
* than the RFC's standard (seconds since the start of 1900). We have a
* test  for  this,  using  the  interval  between 1900 and 1970 as the
* threshold.  This may break on some machines when 2040 rolls around.
*/

#include "V.h"
#include "memchunk.h"
#include "sys_in.h"
#include "sys_netdb.h"
#include "sys_socket.h"
#include "sys_time.h"
#include "sys_types.h"
#if defined(AIX)
#include "sys_select.h"
#endif

#define UDPBUF 16	/* We only need space for a Unix timestamp */

#ifndef MAXHOSTNAMELEN	/* Longest allowed hostname */
#define MAXHOSTNAMELEN 256
#endif

int   udp_len;
SKin  udp_adr;

#define SECpMIN	60	/* Seconds per minute */
#define MINpHR  60	/* Minutes per hour */
#define HRpDAY  24	/* Hours per day */
#define DAYpYR 365	/* Days per year, not including leap days */
#define SECpHR (SECpMIN * MINpHR)
#define MINpDAY (MINpHR * HRpDAY)
#define SECpDAY (SECpMIN * MINpDAY)
#define SECpYR  (SECpDAY * DAYpYR)
#define DAYS ((70 * DAYpYR) + 17)
#define USECS ((U32)DAYS * (U32)SECpDAY)	/* Seconds in 1900-1969  */

long vSECpMIN = SECpMIN;	/* Seconds per minute */
long vMINpHR = MINpHR;	/* Minutes per hour */
long vHRpDAY = HRpDAY;	/* Hours per day */
long vDAYpYR = DAYpYR;	/* Days per year, not including leap days */
long vSECpHR = SECpHR;
long vMINpDAY = MINpDAY;
long vSECpDAY = SECpDAY;
long vSECpYR = SECpYR;
long vDAYS = DAYS;
long vUSECS = USECS;	/*  Seconds in 1900-1969  */

Flag adjust = 1;	/* Do the adjustment */
CP   hostname = 0;	/* Our hostname, if known */
int  howlong = 1;	/* How many times to repeat */
int  loops = 0;		/* Loop count */
Str  msg = {0};
int  msgs = 0;		/* How many messages received */
int  interval = SECpHR;	/* Time between repeats */
int  udpport = 37;	/* 37 is the standard UDP clock port */
int  sock = -1;
SKin sndr = {0};	/* Address of sender */
int  sndl = 0;		/* Actual size of sndr */
TIMV timeout = {5,0};
int  use_rtime = 0;	/* Use rtime() routine */
int  use_tserv = 1;	/* Use UDP time service */

extern SKin udp_adr;

#define Auth struct _auth_
Auth {
	char* name;
	SKin  addr;
	TIMV  time;
	TIMV  err;
	Auth* next;
};
Auth* authq = 0;
int   auths = 0;

main(ac,av)
	int   ac;
	char**av;
{	int   a, i, r;
	int   c0, c1;
	int   a1, a2, a3, a4;
	char* p;
	char* cp;
	int   matches, requests, replies;
	TIMV  err;
	TIMV  adj;
	U32*  tp, tv;
	U32   looptime;
	Auth* ap;
	fd_set efdset, rfdset;
	HOST* hp;

	ac = Vinit(ac,av);
	for (a = 1; a < ac; ++a) {
		V5 "Arg %2d: %s",a,Dsps(av[a],-1) D;
		Switch(c0 = av[a][0]) {
		case '-':
		case '+':
			V5 "Opt %2d: %s",a,Dsps(av[a],-1) D;
			Switch(c1 = av[a][1]) {
			case 'a': case 'A':
				adjust = (c0 == '+');
				V3 "adjust=%d interval=%d.",adjust,interval D;
				break;
			case 'D':
			case 'd':	/* Debug mode: 1-digit level and file name */
			case 'v':
			case 'V':	/* Verbose mode is same as debug */
				Vopt(av[a]+2);
				break;
			case 'f': case 'F':
				if (howlong = (c0 == '+')) {
					if (sscanf(av[a]+2,"%d",&howlong) < 1) {
						howlong = 1000000000;
					}
				}
				V3 "howlong=%d interval=%d.",howlong,interval D;
				break;
			case 'i': case 'I':
				if (c0 == '+') {
					if (sscanf(av[a]+2,"%d",&interval) < 1)
						interval = SECpHR;
				} else {
					howlong = 1;
				}
				V3 "howlong=%d interval=%d.",howlong,interval D;
				break;
			case 'r': case 'R':
				if (use_rtime = (c0 == '+')) {
					sscanf(av[a]+2,"%d",&interval);
				/*	use_tserv = 0;	*/
				}
				V3 "use_rtime=%d interval=%d.",use_rtime,interval D;
				break;
			case 't': case 'T':
				if (use_tserv = (c0 == '+')) {
					if (sscanf(av[a]+2,"%d",&udpport) < 1) {
						udpport = 37;
					}
				}
				V3 "use_tserv=%d udpport=%d.",use_tserv,udpport D;
				break;
			default:
				V5 "Opt %2d: %s ignored.",a,Dsps(av[a],-1) D;
			}
			break;
		default:
			V5 "Arg %2d: %s is auth name.",a,Dsps(av[a],-1) D;
			if (ap = (Auth*)GetChunk(sizeof(Auth),"Auth")) {
				ap->next = authq;
				authq = ap;
				ap->name = av[a];
			}
		}
	}
	if ((sock = udpsocket(0)) < 0) {
		V2 "Can't get UDP socket [Err %d=%s=%s]",Errinfo D;
		Fail;
	}
	for (ap = authq; ap; ap = ap->next) {
		V5 "Auth: %s",Dsps(ap->name,-1) D;
		if (sscanf(ap->name,"%u.%u.%u.%u",&a1,&a2,&a3,&a4) == 4) {
			V5 "IP address %s recognized.",ap->name D;
			cp = (CP)&(ap->addr.sin_addr.s_addr);
			*cp++ = a1;
			*cp++ = a2;
			*cp++ = a3;
			*cp   = a4;
			ap->addr.sin_port = htons(udpport);
			ap->addr.sin_family = AF_INET;
			V5 "Addr: %s [from sscanf]",SymSockAddr(&ap->addr) D;
		} else {
			V5 "Host name %s recognized.",ap->name D;
			if (hp = Gethostbyname(ap->name)) {
				V5 "Host %s found.",ap->name D;
				Bcopy(hp->h_addr_list[0],&(ap->addr.sin_addr.s_addr),4);
				ap->addr.sin_port = htons(udpport);
				ap->addr.sin_family = AF_INET;
				H6(&ap->addr,8,"addr");
				V5 "Addr: %s [from Gethostbyname]",SymSockAddr(&ap->addr) D;
			} else {
				V2 "### %s unknown.",Dsps(ap->name,-1) D;
			}
		}
	}
	if (!MinBlkM(&msg,UDPBUF,"msg")) Fail;
	Vtime();
	looptime = Vtiml;
loop:
	Vchklog();
	if (loops >= howlong) {
		V2 "Quit after %d samples.",loops D;
		errno = 0;
		Done;
	}
	if (loops && ((i = (interval - (Vtiml - looptime))) > 0)) {
		P5 "%s\tWait for %d seconds to elapse ...",Vtimp,i D;
		Sleep(i);
		P5 "%s\tIt's now %d seconds later.",Vtime(),i D;
	}
	++loops;
	V5 "Loop %d ...",loops D;
	Vtime();
	looptime = Vtiml;
	msg.l = 0;
	timeout.tv_sec = Max(1,interval);
	V5 "timeout is %d.%03d sec.",timeout.tv_sec,timeout.tv_usec/1000 D;
#ifdef HAS_rtime
	if (use_rtime) {
		V5 "Using rtime() ..." D;
		for (ap = authq; ap; ap = ap->next) {
			V5 "Auth: %s",Dsps(ap->name,-1) D;
			if (IAhost(ap->addr)) {
				V5 "Auth: %s addr %s",Dsps(ap->name,-1),SymSockAddr(&ap->addr) D;
				if (rtime(&ap->addr,&ap->time,&timeout) < 0) {
					V5 "Auth: %s addr %s failed [Err %d=%s=%s]"
						,Dsps(ap->name,-1),SymSockAddr(&ap->addr),Errinfo D;
					continue;
				}
				V4 "%s	Time %08X.%08X at %s %s."
					,Vtime()
					,ap->time.tv_sec,ap->time.tv_usec
					,Dsps(ap->name,-1),SymSockAddr(&ap->addr)
				 D;
				ap->err.tv_sec  = ap->time.tv_sec - Vtimev.tv_sec;
				ap->err.tv_usec = ap->time.tv_usec - Vtimev.tv_usec;
				if (ap->err.tv_usec < 0) {
					ap->err.tv_usec += 1000000;
					ap->err.tv_sec  -= 1;
				}
				V4 "%s	%s %s is off by %d.%06d sec."
					,Vtime()
					,Dsps(ap->name,-1),SymSockAddr(&ap->addr)
					,ap->err.tv_sec,ap->err.tv_usec
				 D;
			} else {
				V5 "Auth: %s ignored [no IP address]",Dsps(ap->name,-1) D;
			}
		}
	}
#endif	/* No HAS_rtime */
	if (use_tserv) {
		V5 "Using UDP time service ..." D;
		requests = 0;
		for (ap = authq; ap; ap = ap->next) {
			V5 "Auth: %s",Dsps(ap->name,-1) D;
			if (IAhost(ap->addr)) {
				V5 "Auth: %s addr %s",Dsps(ap->name,-1),SymSockAddr(&ap->addr) D;
				i = Sendto(sock,msg.v,msg.l,0,&ap->addr,sizeof(SKin));
				if (i < 0) {
					V5 "### Can't send to %s %s [Err %d=%s=%s]"
						,ap->name,SymSockAddr(&ap->addr),Errinfo D;
				}
				++requests;	/* Count the messages sent */
			} else {
				V5 "Auth: %s ignored [no IP address]",Dsps(ap->name,-1) D;
			}
			ap->time.tv_sec = 0;	/* This one's offset is unknown */
		}
		replies = 0;
		while ((replies < requests) && ((i = (interval - (Vtiml - looptime))) > 0)) {
			V5 "Select has %d seconds left.",i D;
			FD_ZERO(&rfdset);
			FD_ZERO(&efdset);
			FD_SET(sock,&rfdset);
			FD_SET(sock,&efdset);
			timeout.tv_sec = Max(1,i);
			i = Select(sock+1,&rfdset,0,&efdset,&timeout);
			if (i < 0) {
				V2 "Select returned %d, error %d=%s=%s",i,Errinfo D;
				Fail;
			}
			if (!FD_ISSET(sock,&rfdset)) {
				V2 "### rfdset doesn't have bit set for file %d.",sock D;
				Fail;
			}
			sndl = sizeof(sndr);

			r = Recvfrom(sock, msg.v, UDPBUF, 0, &sndr, &sndl);
			if (r <= 0) {
				V3 "Nothing received [r=%d Err %d=%s=%s]",r,Errinfo D;
				Loop;
			}
			++replies;			/* Count the replies */
			msg.v[msg.l = r] = 0;
			tp = (U32*)msg.v;	/* The host's time */
			tv = ntohl(*tp);	/* The host's time  */
			V4 "Msg v:%08lX l=%d tp=%08lX tv=%08lX=%u ...",msg.v,msg.l,tp,tv,tv D;
			if (tv >= (U32)USECS) {	/* The RFC says that the base is 1970/01/01 00:00:00 UTC) */
				V4 "Time %08lX=%u >= USECS=%08lX=%u; adjust.",tv,tv,USECS,USECS D;
				tv -= (U32)USECS;	/* The host's time (seconds since 1970/01/01 00:00:00 UTC) */
			} else {				/* Some hosts use the Unix timestamp */
				V4 "Time %08lX=%u < USECS=%08lX=%u; don't adjust.",tv,tv,USECS,USECS D;
			}
			p = Vtimstr(tv);	/* Convert to ASCII */
			V4 "Recv UDP %d bytes file %d addr %s.",r,sock,SymSockAddr(&sndr) D;
			P3 "%s\tTime %08X = %s from %s.",Vtimp,*tp,p,SymSockAddr(&sndr) D;
			Vtime();
			P3 "%s\tTime %08X = %s here %s.",Vtimp,Vtiml,Vtimp,SymSockAddr(&udp_adr) D;
			H4(msg.v, r, "UDPin");
			++msgs;
			matches = 0;
			for (ap = authq; ap; ap = ap->next) {
				if (IAhost(sndr) == IAhost(ap->addr)) {
					V5 "Match." D;
					V5 "Match ap=%08X.",ap D;
					V5 "Match ap=%08X name=%s.",ap,N(ap->name) D;
					V2 "%s\t%s matches.",N(ap->name),SymSockAddr(&ap->addr) D;
					++matches;
					ap->err.tv_sec = (ap->time.tv_sec = tv) - Vtiml;
					ap->err.tv_usec = 0;
					V2 "%s\t%s is off by %d.%06d sec."
						,ap->name,SymSockAddr(&ap->addr),ap->err.tv_sec,ap->err.tv_usec D;
				}
			}
			if (!matches) {
				V5 "No matches." D;
				P2 "%s\t%s not in our address list.",Vtimp,SymSockAddr(&sndr) D;
			}
		}
		V5 "We have received %d messages total.",msgs D;
	}
	err.tv_sec = err.tv_usec = auths = 0;
	for (ap = authq; ap; ap = ap->next) {
		V5 "Auth: %s",Dsps(ap->name,-1) D;
		if (IAhost(ap->addr)) {
			V5 "Auth: %s addr %s",Dsps(ap->name,-1),SymSockAddr(&ap->addr) D;
			P3 "%s\t%s %s is off by %d.%06d sec."
				,Vtimp,ap->name,SymSockAddr(&ap->addr),ap->err.tv_sec,ap->err.tv_usec D;
			err.tv_sec  += ap->err.tv_sec;
			err.tv_usec += ap->err.tv_usec;
			++auths;
			P3 "%s\t%s %s gives err %d.%06d sec; auths=%d."
				,Vtimp,ap->name,SymSockAddr(&ap->addr),err.tv_sec,err.tv_usec,auths D;
		}
	}
	if ((auths > 0) && (err.tv_sec || err.tv_usec)) {
		P3 "%s\tTotal err %d.%06d sec; auths=%d."
			,Vtimp,err.tv_sec,err.tv_usec,auths D;
		err.tv_usec /= auths;
		if (i = err.tv_sec % auths)
			err.tv_usec += (1000000 * i) / auths;
		err.tv_sec /= auths;
		if (err.tv_sec < 0 && err.tv_usec > 0) {	/* Mismatched-sign anomaly? */
			P2 "%s\tMean  err %d.%06d sec; auths=%d needs adjusting."
				,Vtimp,err.tv_sec,err.tv_usec,auths D;
			err.tv_sec  += 1;
			err.tv_usec -= 1000000;
		}
		P2 "%s\tMean  err %d.%06d sec; auths=%d."
			,Vtimp,err.tv_sec,err.tv_usec,auths D;
	}
	if (adjust && err.tv_sec) {
	/*	if (err.tv_usec < 0)	*/
	/*		err.tv_usec = 0;	*/
		P2 "%s\tAdjust by %4d.%06d sec; auths=%d."
			,Vtimp,err.tv_sec,err.tv_usec,auths D;
		errno = 0;
		i = adjtime(&err,&adj);
		if (i < 0)
			V2 "adjtime gave %d [Err %d=%s=%s]",i,Errinfo D;
		P2 "%s\tAdjust was %3d.%06d sec; auths=%d."
			,Vtimp,adj.tv_sec,adj.tv_usec,auths D;
	} else {
		if (!adjust)
			V2 "Adjustment suppressed by -a option." D;
		if (auths <= 0)
			V2 "%s\t%d responses from time authorities.",Vtimp,auths D;
		P2 "%s\tMean clock error was %d.%06d sec."
			,Vtimp,err.tv_sec,err.tv_usec D;
	}
	Loop;

fail:
done:
	Exit(errno);	/* Logged exit */
	exit(errno);	/* Paranoia */
	_exit(errno);	/* Sheer paranoia */
}

/*
* Create socket from which to read.
*/
FCT udpsocket(port)
{	int sock;
	HOST* hp;
	Fenter("udpsocket");
	V6 "Called for port %d.",port D;
	sock = Socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		V1 "Can't get DGRAM socket [Err %d=%s=%s]",Errinfo D;
		Fail;
	}
/*
* Create address with wildcards.
*/
	if (!hostname) {
		if (!(hostname = (char*)GetChunk(MAXHOSTNAMELEN,"hostname"))) {
			Fail;
		}
		if (Gethostname(hostname, MAXHOSTNAMELEN)) {
			V1 "Can't get hostname [Err %d=%s=%s]",Errinfo D;
			Fail;
		}
	}
	V3 "Host: %s",Dsps(hostname,-1) D;
	hp = Gethostbyn(hostname);
	Bcopy(hp->h_addr,&(udp_adr.sin_addr.s_addr),hp->h_length);
	udp_adr.sin_family = AF_INET;
	udp_adr.sin_port = htons(port);
	if (Vlvl > 3) DmpSKin(&udp_adr);
	H6(&udp_adr,sizeof(udp_adr),"udp_adr");
	if (Bind(sock,&udp_adr,sizeof(udp_adr)) < 0) {
		V1 "Can't bind to address %s.%d [Err %d=%s=%s]",
			ipdot(&udp_adr.sin_addr),
			ntohs( udp_adr.sin_port),
			Errinfo D;
		Fail;
	}
/*
* Find assigned port value and print it out.
*/
	udp_len = sizeof(udp_adr);
	if (Getsockname(sock, &udp_adr, &udp_len)) {
		V1 "Can't get socket address for file %d [Err %d=%s=%s]",sock,Errinfo D;
		Fail;
	}
	H6(&udp_adr,sizeof(udp_adr),"udp_adr");
	printf("UDP socket has address %s port %d.\n",
		ipdot(&udp_adr.sin_addr),
		ntohs( udp_adr.sin_port));
fail:
	Fexit;
	return sock;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* CALL:
*   DmpSKin(p)
*   	SKin *p;
*
* DESCRIPTION:
*   Symbolic dump of a struct sockaddr_in, i.e., an Internet address  struct.
*   See  also  SymSockAdd() for a routine that returns a printable string for
*   including such things in output messages.
*
* AUTHOR:
*   John Chambers.
*
* CHANGES:
*
*/
DmpSKin(p)
	SKin *p;
{
	V1 "\tSKin fmly=%u addr=%08X.%04X=%s.%u."
		,p->sin_family
		,p->sin_addr
		,p->sin_port
		,ipdot(&p->sin_addr)
		,ntohs(p->sin_port)
	 D;
}
