[comp.protocols.time.ntp] A poor man's WWV receiver

turner@ksr.com (James M. Turner) (11/06/90)

Here's a program I wrote to call the Naval Observatory and set the time.
It seems to be accurate to about 50 milliseconds on a Sun 3.  I know it
works on SunOS 4.0.3, if people get it working on other systems, please
send me the changes and I'll merge them into the sources for future
releases.

Instructions on compiling and using the software can be found in the
source.

------------------------------ Cut Here ------------------------------
/* UTCCLOCK.C - Set time from US naval Observatory via modem
   Copyright (C) 1990, James M. Turner

                               ***

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

                               ***

   utcclock allows us mortals without cessium clocks and WWV radio
   receivers to keep our time reasonable close to UTC.

   Experiments on a Sun 3/60 had local time within 50 milliseconds of
   UTC as provided by the US Naval Observatory.  Not perfect, but better than
   trying to set it by hand.

   Usage: utcclock [-S] [-s speed] [-l device] [-n number]

   Where speed is the baud rate to use, device is the device to call on,
   and number is the phone number to dial.  If the -S option is used, the
   time will be set, otherwise only the delta difference is reported.  You
   must be root to set the time.  Because it uses the UUCP locking mechanism,
   it needs write permission to /usr/spool/uucp.  I installed it setuid with
   owner = uucp on my system.  If you install it setuid to root, anyone will
   be able to set the time.

   On SunOS 4.0.3, compile with:

   cc -O -o utcclock utcclock.c

   I suggest installing it in /usr/local/etc

   Have fun, and happy ticking.

   James Turner
   (turner@ksr.com)
   617-895-9480

   $Log:	utcclock.c,v $
 * Revision 1.4  90/11/05  16:27:56  turner
 * Added GPL notice
 * 
 * Revision 1.3  90/11/05  16:06:06  turner
 * Added exclusive locking of device, better leap-year computation.
 * 
   */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <termio.h>

char version[] = "$Id: utcclock.c,v 1.4 90/11/05 16:27:56 turner Exp $";

/* PLEASE CHECK AND SET THE VALUES BELOW THIS LINE !!! */

/* Default device to use.  Should be exclusive-locking if possible */
#define TTYPORT "cua0"

/* Phone number to dial.  Please verify that you receive tone on this number
   (we wouldnt want to harrass little old ladies with modem tones, now would
   we?)
   */
#define NUMBER "12026530351"	/* US Naval Observatory */

/* Format of string to send to modem with number in %s */
#define DIALSTRING "ATDT%s\r"

/* Default baud rate to use on line */
#define BAUDRATE B1200

/* Number of seconds of data to read */
#define NCOUNT 5

/* Locking file for UUCP. %s will be replaced by "ntp" and the device name */
#define LOCKFILE "/usr/spool/uucp/LCK..%s"

/* END OF USER CONFIGURABLE SECTION */

#define FIRST_YEAR 1988
#define FIRST_VALUE 47160	/* The day value of Dec 31, FIRST_YEAR-1 */

int months[] = {
  31, 28,31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int modem;			/* Device Descriptor for Modem Line */
char *timeout_reason=NULL;	/* If we timeout, where are we in the code? */

char lck1[80], lck2[80];

/* This code handles the cleanup (shutting down the modem) if you
abort for some reason */

void
exit_handler(sig, code, scp, addr)
int sig, code;
struct sigcontext *scp;
char *addr;
{
  int flags =  (TIOCM_DTR | TIOCM_LE);
  ioctl(modem, TIOCMBIC, &flags);
  if (sig == SIGALRM)
    printf("utcclock: timeout while %s\n", timeout_reason);
  if (timeout_reason != NULL) {
    unlink(lck1);
    unlink(lck2);
  }
  exit(0);
}

struct sigvec vec = {
  exit_handler,
  0,
  0};

/* Returns number of days in the given year.
 * Remember, a leap year is a year which is evenly divisible by 4,
 * but not a century, unless it is a century divisible by 400
 */
static
daysinyear(thisyear)
    int thisyear;
{
    if ((thisyear % 4) == 0 && (((thisyear % 100) != 0) ||
           ((thisyear % 400) == 0)))
       return(366);
    return(365);
}

main(argc, argv)
int argc;
char **argv;
{
  extern char *optarg;
  extern int optind;
  int fd1, flags;
  struct sigvec ovec;
  struct termio tty;
  char c, *OptLine, *OptNumber, option, device[50];
  struct tm tm;
  struct timeval del, del1;
  struct timezone tzp;
  struct timeb tp;
  time_t i;
  long utc_time;
  int hh, mm, ss, dd, yy, dummy, milli[3], m, n = 0, r, k=0, des,
      yydd, year, month, OptSetme = 0, OptSpeed = BAUDRATE;
  char buffer[80];
  

  /* Make sure that timeouts and terminations clean up after themselves */
  if (sigvec(SIGTERM, &vec, &ovec) != 0) {
    perror("Sigvec:");
    exit(-1);
  }
  if (sigvec(SIGINT, &vec, &ovec) != 0) {
    perror("Sigvec:");
    exit(-1);
  }
  if (sigvec(SIGALRM, &vec, &ovec) != 0) {
    perror("Sigvec:");
    exit(-1);
  }

  OptLine = TTYPORT;
  OptNumber = NUMBER;

  /* Read in the options */
  while ((option = getopt(argc, argv, "Sl:s:n:")) != -1) {
    switch (option) {
    case 'S':
      OptSetme = 1;
      break;
    case 'l':
      OptLine = optarg;
      break;
    case 'n':
      OptNumber = optarg;
      break;
    case 's':
      OptSpeed = atoi(optarg);
      switch (OptSpeed) {
      case 300:
	OptSpeed = B300;
	break;
      case 1200:
	OptSpeed = B1200;
	break;
      case 2400:
	OptSpeed = B2400;
	break;
      case 4800:
	OptSpeed = B4800;
	break;
      case 9600:
	OptSpeed = B9600;
	break;
      case 19200:
	OptSpeed = B19200;
	break;
      otherwise:
	fprintf(stderr, "utcclock: Illegal baud rate\n");
	exit(2);
      }
      break;
    case '?':
      fprintf(stderr, "Usage: utcclock [-S] [-s speed] [-l line] [n number]\n");
      exit(2);
      break;
    }
  }

  sprintf(device, "/dev/%s", OptLine);
  /* 60 Seconds to get the mess worked out */
  alarm(60);
  sprintf(lck1, LOCKFILE, "ntp");
  sprintf(lck2, LOCKFILE, OptLine);

  if ((n = open(lck1, O_CREAT|O_EXCL)) == -1) {
    fprintf(stderr, "utcclock: utcclock already running\n");
    exit(2);
  }
  close(n);
  if ((m = open(lck2, O_CREAT|O_EXCL)) == -1) {
    fprintf(stderr, "utcclock: port in use\n");
    unlink(lck1);
    exit(2);
  }
  close(m);

  timeout_reason = "opening device";
  if ((modem = open(device, O_RDWR|O_NDELAY|O_EXCL)) == -1) {
    perror("utcclock");
    exit(2);
  }
  timeout_reason = "setting line parameters";
  if (ioctl(des, TCGETA, &tty) == 0) {
    tty.c_cflag &= ~CBAUD;
    tty.c_cflag |= (CLOCAL | OptSpeed);
  }
  ioctl(des, TCSETA, &tty);
  fcntl(modem,F_SETFL,0);	/* Turn off NDELAY. */
  /* Mom says we have to reopen for effect */
  fd1 = open(device, O_RDWR|O_EXCL);
  close(modem);			/* Close old line */
  modem = fd1;

  /* dial utc */
  timeout_reason = "dialing";
  ioctl(modem, TCFLSH, 0);	/* Flush any old characters */
  sprintf(buffer, DIALSTRING, OptNumber); /* Change for non-hayes */
  write(modem, buffer, strlen(buffer));

  /* Now we read characters until we see the first * from the remote
   side.  I hope your modem doesn't report messages with a *
   */
  timeout_reason = "Looking for *";
  for (n = 0, buffer[0] = ' '; (n > -1), (buffer[0] != '*');
       n = read(modem, buffer, 1));
  if (n == -1) exit(2);

  yy = 0;			/* Set year = 0 */
  n = 0;			/* Set character counter = 0 */
  while (k < NCOUNT) {		/* Until we get NCOUNT seconds of data */
    i = read(modem, buffer+n, 1); /* Read a character */
    if (i <= 0) exit(-1);	/* On EOF, punt */
    if (buffer[n] == '*') {	/* If second-marker */
      if (yy != 0) {		/* Have we got legal data? */
	if (k == (NCOUNT-1)) {	/* And have we seen NCOUNT seconds? */
	  ftime(&tp);		/* Get our local time for delta */
	  if (OptSetme) {	/* If we set time */
	    r = settimeofday(&del1, &tzp); /* Set to calculated value */
	    if (r < 0)		/* Did we get an error? */
	      perror("settimeofday");
	  }
	  if (del1.tv_sec > tp.time) { /* If value is negative */
	    if (tp.millitm != 0) { /* And some part of it is millitm */
	      tp.time++;	/* Then delta is one second less */
	      tp.millitm = 1000 - tp.millitm; /* Plus 1.0 - millitm */
	    }
	    printf("Our clock slow by %d.%03d seconds\n",
		   del1.tv_sec - tp.time, tp.millitm);
	  } else {
	    printf("Our clock fast by %d.%03d seconds\n", 
		   tp.time - del1.tv_sec, tp.millitm);
	  }
	  printf("Universal Coordinated Time exactly %d/%d/%d %d:%02d:%02d\n", 
		 month, dd, yy, hh, mm, ss);
	}
	/* If we aren't ready to set the time yet, add one to the timestamp
	   and one to the good marker count */
	del1.tv_sec++;		
	k++;
      }
    } else {
      /* If we got a CR, we figure out the time sent */
      if (buffer[n] == '\n' || buffer[n] == 'r') {
	buffer[n] = 0;		/* Terminate buffer */
	/* If we haven't read a good value, and there are characters in the
	   buffer, and the string parses correctly, then calculate time
	   */
	if ((yy == 0) && 
	    (n != 0) &&
	    (sscanf(buffer, "%d %d %2d%2d%2d UTC", &yydd, &dd, &hh, &mm, &ss) 
	     == 5)) {

	  yydd -= FIRST_VALUE;	/* Subtract day value of Dec 31st */
	  for (year = FIRST_YEAR; (yydd > daysinyear(year)); year++)
	       /* Decrement until under 1 year left */
	       yydd -= daysinyear(year);
	  yy = year - 1900; /* Set year number */
	  if (daysinyear(year) == 366)
	       months[1] = 29; /* Handle leap year */
	  for (month = 0; (dd > months[month]); month++)
	    dd -= months[month];

	  tm.tm_sec = ss;	/* Stuff seconds in second field */
	  tm.tm_min = mm;	/* Stuff minutes in minute field */
	  tm.tm_hour = hh;	/* Stuff hours in hour field */
	  tm.tm_year = yy;	/* Stuff year in year field */
	  tm.tm_mday = dd;	/* Stuff day of month */
	  tm.tm_mon = month;	/* Stuff month */
	  utc_time = ((long)timegm(&tm)); /* Get gmtime version of time */
	  gettimeofday(&del1, &tzp); /* Get time zone information */
	  del1.tv_usec = 0;	/* Always exactly on second */
	  del1.tv_sec = utc_time; /* * is for *NEXT* second */
	}
	n = 0;			/* Reset character counter */
      } else {
	n++;			/* Store character */
	buffer[n] = 0;		/* Terminate */
      }
    }
  }
  flags = TIOCM_DTR;		/* Clean up after success */
  ioctl(modem, TIOCMBIC, &flags);
  unlink(lck1);
  unlink(lck2);
  exit(0);
}
-- 
Name:    James M. Turner            * Great Moments in Aviation #21: While on a
Company: Kendall Square Research    * NDB approach into Hanscom, pilot Ted Hertz
Email:   turner@ksr.com, ksr!turner * accidently tunes WEEI on his NAV instead
Phone:   (617) 895-9400             * of the LOM, and lands on top of the Pru.

mackey@scs.fiu.edu (11/08/90)

In article <885@ksr.com> turner@ksr.com (James M. Turner) writes:
>Here's a program I wrote to call the Naval Observatory and set the time
>------------------------------ Cut Here ------------------------------
>/* UTCCLOCK.C - Set time from US naval Observatory via modem
>   Copyright (C) 1990, James M. Turner

The program referenced here uses a modem to dial into the USNO.
Does anyone know of a way of doing the same thing via an
internet connection?  Presumably ``rdate'' would work reasonably
well if the connection is fast.  But what is the Naval Obs.'s
internet address?  Is there a better way than rdate?

Jim.Thompson@Central.Sun.COM (Jim Thompson) (11/08/90)

	From ntp-relay@trantor.umd.edu Wed Nov  7 13:53:25 1990
	Return-Path: <ntp-relay@trantor.umd.edu>
	Received: from Central.Sun.COM (texsun) by hosaka (4.1/SMI-4.1)
		id AA03860; Wed, 7 Nov 90 13:53:24 CST
	Received: from Sun.COM (sun-barr.EBay.Sun.COM) by Central.Sun.COM (4.1/SMI-4.1-900117)
		id AA10482; Wed, 7 Nov 90 13:53:22 CST
	Received: from trantor.umd.edu by Sun.COM (4.1/SMI-4.1)
		id AA08434; Wed, 7 Nov 90 11:53:18 PST
	Received: by trantor.umd.edu (5.64/1.34)
		id AA14738; Wed, 7 Nov 90 14:24:06 -0500
	Received: from ucbvax.Berkeley.EDU by trantor.umd.edu (5.64/1.34)
		id AA14734; Wed, 7 Nov 90 14:24:02 -0500
	Received: by ucbvax.Berkeley.EDU (5.63/1.42)
		id AA22380; Wed, 7 Nov 90 11:18:35 -0800
	Received: from USENET by ucbvax.Berkeley.EDU with netnews
		for ntp@trantor.umd.edu (ntp@trantor.umd.edu)
		(contact usenet@ucbvax.Berkeley.EDU if you have questions)
	Date: 7 Nov 90 18:53:33 GMT
	From: swrinde!zaphod.mps.ohio-state.edu!uakari.primate.wisc.edu!uflorida!kluge!scs!mackey@ucsd.edu
	Organization: %BS-F-QXCD, Bullshit quota exceeded, core dumped.
	Subject: Re: A poor man's WWV receiver (via modem)
	Message-Id: <1632@kluge.fiu.edu>
	Sender: ntp-relay@trantor.umd.edu
	To: ntp@trantor.umd.edu
	Status: R

	In article <885@ksr.com> turner@ksr.com (James M. Turner) writes:
	>Here's a program I wrote to call the Naval Observatory and set the time
	>------------------------------ Cut Here ------------------------------
	>/* UTCCLOCK.C - Set time from US naval Observatory via modem
	>   Copyright (C) 1990, James M. Turner

	The program referenced here uses a modem to dial into the USNO.
	Does anyone know of a way of doing the same thing via an
	internet connection?  Presumably ``rdate'' would work reasonably
	well if the connection is fast.  But what is the Naval Obs.'s
	internet address?  Is there a better way than rdate?

(x)ntpdate?

Jim

gavron@vesta.sunquest.com (Ehud Gavron) (11/08/90)

In article <1632@kluge.fiu.edu>, mackey@scs.fiu.edu writes...
#In article <885@ksr.com> turner@ksr.com (James M. Turner) writes:
#>Here's a program I wrote to call the Naval Observatory and set the time
#>------------------------------ Cut Here ------------------------------
#>/* UTCCLOCK.C - Set time from US naval Observatory via modem
#>   Copyright (C) 1990, James M. Turner
# 
#The program referenced here uses a modem to dial into the USNO.
#Does anyone know of a way of doing the same thing via an
#internet connection?  Presumably ``rdate'' would work reasonably
#well if the connection is fast.  But what is the Naval Obs.'s
#internet address?  Is there a better way than rdate?

	How about running ntp?

	;-) :-) ;-) :-)

	Ehud

/----------------------------------------------------------------------------\
| Ehud Gavron, Systems analyst  | gavron@vesta.sunquest.com      (Internet)  |
| Sunquest Information Systems  | uunet!sunquest!gavron          (UUCP)      |
| 930 N. Finance Center Drive   | gavron@lampf                   (BITNET)    |
| Tucson, Arizona, 85710        | (602)722-7546/885-7700 x.2546  (AT&Tnet)   |
|----------------------------------------------------------------------------|
|           your cute quote here                                             |
\----------------------------------------------------------------------------/

Mills@udel.edu (11/11/90)

[?? no sender full name ??]

So far as I have been able to determine, there is no IP connectivity
for USNO. I have it on personal communication that they have plans
to do this, but no bucks.

Dave

medin@NSIPO.NASA.GOV ("Milo S. Medin", NASA ARC NSI Project Office) (11/11/90)

Not true!   The USNO is connected to the Internet via the NASA Science Net.  
We have a 56 Kbps link from there to GSFC, whre it hits the NSN backbone, and
then via FIX's to the NSFNET and other US Government backbones.


U.S. Naval Observatory (NET-USNO)
   Observatory Circle NW
   Washington, DC 20390

   NetName: USNO
   NetNumber: 192.5.41.0

   Coordinator:
      Withington, F. Neville  (NW33)  FNW@USNO01.UNSO.NAVY.MIL
      (202) 653-0588

   Record last updated on 23-Oct-90.


It's also reachable:

traceroute to usno01.usno.navy.mil (192.5.41.10), 30 hops max, 40 byte packets
 1  arc-nas-gw (128.102.16.5)  10 ms  10 ms  0 ms
 2  ARC1.NSN.NASA.GOV (192.52.195.2)  10 ms  0 ms  10 ms
 3  GSFC1.NSN.NASA.GOV (128.161.2.3)  90 ms  90 ms  100 ms
 4  GSFC3.NSN.NASA.GOV (128.161.16.33)  90 ms  90 ms  100 ms
 5  USNO.NSN.NASA.GOV (128.161.38.35)  110 ms  120 ms  130 ms
 6  * * *


Perhaps you should tell your friends there to connect up to this network...

						Thanks,
						    Milo

Mills@udel.edu (11/11/90)

Milo,

My statemet nt stands. Lauro   a Charron tells me USNO doe s  s not   provide
a USNO te imestamp. Regardless of your NS a ASA prov be, I am told thter    jere    here
is no USNO timestamp worthy of international chime. I wqou   ould be
most gald   lad of a proof  ff t   f to the contrary.

Dave

medin@NSIPO.NASA.GOV ("Milo S. Medin", NASA ARC NSI Project Office) (11/11/90)

Well, I don't think any of the clocks are wired up to anything that speaks
IP, but there is Internet connectivity at the site...

						Thanks,
						   Milo