🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: aus.sources
Article #10

AOTC atomic clock

#10
From: Michael Henning
Date: Tue, 22 Dec 1992 04:11
509 lines
14097 bytes
I've just written a hack to automatically set the system time on our
machine from AOTC's atomic clock. For those who don't know, AOTC offer
a time service at 1200 Baud. The number for Brisbane is (07) 221-7033.
I don't know about other cities, but I guess directory assistance
may be able to help.

I am using the MHSnet VCcall program to invoke the code that
sets the system time (details below).
If you don't have MHSnet, ACSnet will work if you write yourself
a call script that invokes the setclock program.
If you can get your UUCP to invoke a program from a chat script,
you should be able to use that too.

Anyway, below is some code that can set the time for you. I've included
some explanations for how to configure it into MHSnet. You should be
able to get going with that. The usual disclaimers apply. If my code
sets your machine on fire - tough luck.

The whole process from matching the date string (at 1200 Baud) to finally
setting the system clock takes less than 0.75 seconds on our machine.
For our purposes, that is accurate enough.

							Cheers,

								Michi.


To get MHSnet to make the call for you, you need a link for which to
establish a call. The easiest way to do this is to add a pseudo host
and create a link to it. The following entries in _state/commandsfile
do just that.

#
# Pseudo host to use the MHSnet modem dialer to set the time of day.
#
add		N=setclock.O=ptcbu.P=oz.C=au
link		N=ptcburp.O=ptcbu.P=oz.C=au,
		N=setclock.O=ptcbu.P=oz.C=au
		+foreign,+nochange,+dead
linkname	N=setclock.O=ptcbu.P=oz.C=au
		setclock

The host is called "setclock" and placed into the local domain. The
+foreign,+nochange,+dead flags prevent any messages from being sent
to that host. Please use your machine and domain to do this, not
ptcburp.ptcbu.oz.au :-)

Do a "netstate -wrsxC" after changing your commandsfile.

Next, add an entry to _lib/initfile:

call-setclock.ptcbu.oz.au "49 8 * * 1"          setclock/call

This tries to establish a connection to the setclock host once a week.

In $SPOOLDIR, create a directory "setclock", owned by daemon, group daemon,
with permissions 750.

Create a file called "call", owned by daemon, group daemon, with
permissions 750. It contains the following script:


#!/bin/sh

DEVICE=/dev/term/0
TRACE=0
if [ "$TRACE" -gt 0 ]
then
	mdmtrace='M1S61=255'	# Switch speaker on
else
	mdmtrace='M0'		# Be silent
fi
/usr/spool/MHSnet/_lib/VCcall \
	-T${TRACE} \
	-v \
	-D "dialstr=AT${mdmtrace}S50=3DT0,07,2217033" \
	-D "linespeed=19200" \
	-D "modemdev=$DEVICE" \
	-D "timecmd=/usr/local/bin/setclock" \
	-S setclock.cs \
	-X /usr/spool/MHSnet/setclock/call.log \
	setclock.ptcbu.oz.au
exit $?

This simply invokes VCcall with the appropriate arguments.
The dial string here is for a Trailblazer, so you need to adjust it if
you have a different modem.


The call script that calls the clock server and picks out the date
string goes into _call/setclock.cs. I modified one of the generic
call scripts which come with MHSnet to invoke a program to set the date.


/*
**	Call script to set the system time.
**
**	Imports:
**		dialstr		string to cause modem to dial remote site
**		linespeed	speed for modemdev
**		modemdev	device attached to modem
**		timecmd		command to set time
*/

	set dialcount 6;	/* dial attempts */
	set timecount 10;	/* number of tries at reading time */

	/*
	**	Check imported strings.
	*/

chkargs:
	match dialstr UNDEFINED paramerr;
	match linespeed UNDEFINED paramerr;
	match modemdev UNDEFINED paramerr;

start1:
	timeout 60;		/* Wait 1 minute between/for open attempts */
	retry 60;		/* Allow 60 attempts at open => 1 hour max */
	open "dial" modemdev "uucplock" "ondelay" "local";	/* Open with O_NDELAY */
	match RESULT DEVOK openok;
	set reason "Could not open \"" modemdev "\" reason: " RESULT;

openfail:
	fail reason;

paramerr:
	fail "missing some of:\n"
		"\t-D \"dialstr=<ATD command for modem>\"\n"
		"\t-D \"linespeed=<bits/second>\"\n"
		"\t-D \"modemdev=<device pathname for modem>\""
		;

openok:
	device "offdelay";		/* Otherwise reads will return 0 */
	device "speed" linespeed;
	monitor 0;			/* Turn on I/O tracing */

	sleep 1;			/* Because some terminal i/o is slow! */
	write "AT\r";			/* Make sure modem latches on */
	sleep 1;
	write "AT\r";
	sleep 1;
	write "AT\r";
	sleep 1;
	device "flush";
	write "ATZ\r";
	timeout 10;
	expect
		"OK" try_initstr,
		EOF eof,
		TERMINATE terminate,
		TIMEOUT rstmodem;

rstmodem:
	sleep 2;
	write "+++";
clrmodem:
	sleep 2;
	write "ATZ\r";
	timeout 10;
	expect
		"OK" try_initstr,
		EOF eof,
		TERMINATE terminate,
		TIMEOUT nomodem;

try_initstr:
	match initstr UNDEFINED dialing;
	timeout 0;
	sleep 1;
	write initstr "\r";
	timeout 10;
	expect
		"OK" dialing,
		EOF eof,
		TERMINATE terminate,
		TIMEOUT dialing;

nomodem:
	set reason "modem \"" modemdev "\" not responding";
	next nextmodem;

dialing:
	test dialcount dfail;
	timeout 0;
	sleep 1;
	write dialstr "\r";
	timeout 60;
	expect
		"BUSY" busy,
		"NO ANSWER" busy,
		"NO CARRIER" clrmodem,
		"NO DIALTONE" nodial,
		"CONNECT 2400" gettime,
		"CONNECT 1200" gettime,
		"CONNECT" gettime,
		EOF eof,
		TERMINATE terminate,
		TIMEOUT rstmodem;

busy:
	trace "number \"" dialstr "\" busy";
	sleep 30;		/* Give them time to get off the phone */
	next clrmodem;

gettime:
	set DELIMCHARS "\n\r";
	timeout 4;
try_again:
	test timecount no_time;
	device "flush";
	expect
		"[12][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9][ ][0-2][0-9]:[0-5][0-9]:[0-5][0-9]\*" settime,
		TIMEOUT try_again,
		TERMINATE terminate,
		EOF endmodem2;

settime:
	shell timecmd " '"INPUT"'";
	match RESULT "0" endmodem;
	match RESULT "1" bad_format;
	match RESULT "2" bad_date;
	match RESULT "3" too_large;
	match RESULT "4" stime_failed;
	set reason timecmd " '"INPUT"' returned weird exit status: " RESULT;

endmodem:
	device "local";				/* Ignore modem signals */
	sleep 2;
	device "flush";
	write "+++";
	timeout 3;
	expect
		"NO CARRIER" endmodem1,
		"OK" endmodem1,
		EOF endmodem2,
		TERMINATE endmodem0,
		TIMEOUT endmodem1;

endmodem0:
	sleep 2;
endmodem1:
	write "ATH0Z\r";
	timeout 2;
	expect
		"OK" endmodem2,
		EOF endmodem2,
		TERMINATE endmodem2,
		TIMEOUT endmodem2;

endmodem2:
	match finishstr UNDEFINED endmodem3;
	timeout 0;
	write finishstr "\r";
	sleep 1;
endmodem3:
	match reason UNDEFINED out;
	fail reason;
out:
	return;

dfail:
	set reason "too many dial attempts";
	next endmodem;

eof:
	set reason "remote disconnect";
	next endmodem;

nodial:
	set reason "modem \"" modemdev "\" has no dialtone";
	match modemdev2 UNDEFINED endmodem;
	next nextmodem;

terminate:
	set reason "system termination";
	next endmodem;

no_time:
	set reason "cannot get time from connection";
	next endmodem;

bad_format:
	set reason timecmd " '"INPUT"': bad format";
	next endmodem;

bad_date:
	set reason timecmd " '"INPUT"': meaningless date";
	next endmodem;

too_large:
	set reason timecmd " '"INPUT"': time difference too large";
	next endmodem;

stime_failed:
	set reason timecmd " '"INPUT"': stime(2) call failed";
	next endmodem;


Finally, you need to compile and install the code below (set-uid root).
It sets the new date after doing some sanity checks. I added a limit
to prevent the system time being changed by more than 300 seconds.
I figure if my clock is wrong by as much as that, I'd like to know why :-)

If you don't call the program /usr/local/bin/setclock, remember to
change the path passed to the call script from setclock/call.


/*
 * setclock - set the system time using AOTC's atomic clock service.
 *
 * This program is meant to be invoked from a daemon which obtains
 * the current date from AOTC's dial-up atomic clock. The clock produces
 * lines of the form
 *
 *	yyyy-mm-dd hh:mm:ss[*#]
 *
 * The output from the dial-up clock looks like
 *
 * 1992-12-10 10:01:40*
 * 1992-12-10 10:01:41*
 * 1992-12-10 10:01:42*
 * 1992-12-10 10:01:43*
 * 1992-12-10 10:01:44*
 *
 * Each line is printed up to the '*', followed by a brief pause,
 * followed by the '*', CR and LF. The exact mark for the time is
 * the stop-bit/start-bit transition between the CR and LF.
 *
 * For daylight saving time, the '*' is replaced by a '#'.
 *
 * The single argument must be one complete line. Any trailing characters
 * (such as CR/LF) after the '*' or '#' are ignored.
 *
 * The argument is broken into its components, sanity checked and
 * converted into seconds since The Epoch. If the resulting time does
 * not differ from the current system time by more than MAX_ADJ seconds,
 * the system clock is set to the new time.
 *
 * This program must be set-uid root, and should be installed with
 * permissions 4710, owner root, group daemon.
 */

/*****************************************************************************/

#include	<stdlib.h>
#include	<unistd.h>
#include	<libgen.h>
#include	<string.h>
#include	<time.h>

/*****************************************************************************/

#define	FORMAT	"yyyy-mm-dd hh:mm:ss"		/* Format of input date */
#define	USAGE	"Usage: %s \"%s[*#]\"\n"	/* Usage message */

#define	MAX_ADJ	300				/* Max adjust in seconds */

#define	OK		0			/* Exit status if OK */
#define	BAD_FORM	1			/* Bad format of argument */
#define	BAD_DATE	2			/* Meaningless date */
#define	TOO_LARGE	3			/* MAX_ADJ exceeded */
#define	BAD_STIME	4			/* stime(2) failed */

/*****************************************************************************/

static const char	*progname;		/* For error messages */

/*****************************************************************************/

/*
 * Print usage message and exit. If the given date is non-NULL,
 * also complain about an invalid date, showing the invalid string.
 */

static void
usage(int status, const char *date)
{
	if (date != NULL) {
		(void) fprintf(
			stderr, "%s: invalid date: \"%s\"\n", progname, date
		);
	}
	(void) fprintf(stderr, USAGE, progname, FORMAT);
	exit(status);
}

/*****************************************************************************/

int
main(int argc, char *argv[])
{
	char		buf[sizeof(FORMAT) + 1];	/* +1 for '*' or '#' */
	char		*year;
	char		*month;
	char		*day;
	char		*hour;
	char		*minute;
	char		*second;
	struct tm	stm;
	time_t		new_time;

/*
 *	Set program name for error messages.
 */
	progname = basename(argv[0]);
/*
 *	Check usage.
 */
	if (argc != 2)
		usage(BAD_FORM, NULL);
/*
 *	Get rid of any trailing garbage in argument (the AOTC atomic
 *	clock service terminates each line with CR/LF). We clean this
 *	up in argv[1] instead of a copy of it, so we can print nicer
 *	error messages (without any trailing newline).
 */
	/* LINTED */
	if (strlen(argv[1]) < strlen(FORMAT) + 1)
		usage(BAD_FORM, argv[1]);
	argv[1][strlen(FORMAT) + 1] = '\0';
/*
 *	Break up the date into components.
 */
	(void) strncpy(buf, argv[1], strlen(FORMAT) + 1);
	year = buf;
	if ((month = strchr(year, '-')) == NULL)
		usage(BAD_FORM, argv[1]);
	*month++ = '\0';
	if ((day = strchr(month, '-')) == NULL)
		usage(BAD_FORM, argv[1]);
	*day++ = '\0';
	if ((hour = strchr(day, ' ')) == NULL)
		usage(BAD_FORM, argv[1]);
	*hour++ = '\0';
	if ((minute = strchr(hour, ':')) == NULL)
		usage(BAD_FORM, argv[1]);
	*minute++ = '\0';
	if ((second = strchr(minute, ':')) == NULL)
		usage(BAD_FORM, argv[1]);
	*second++ = '\0';
/*
 *	Check fields for validity and build struct tm.
 */
	if (second[2] != '*' && second[2] != '#')
		usage(BAD_FORM, argv[1]);
	stm.tm_isdst = second[2] == '#';
	second[2] = '\0';
	stm.tm_year = atoi(year);
	if (stm.tm_year < 1900)
		usage(BAD_DATE, argv[1]);
	stm.tm_year -= 1900;		/* Origin is the year 1900 */
	stm.tm_mon = atoi(month);
	if (stm.tm_mon < 1 || stm.tm_mon > 12)
		usage(BAD_DATE, argv[1]);
	stm.tm_mon--;			/* Origin is 0 (January) */
	stm.tm_mday = atoi(day);
	if (stm.tm_mday < 1 || stm.tm_mday > 31)
		usage(BAD_DATE, argv[1]);
	stm.tm_hour = atoi(hour);
	if (stm.tm_hour < 0 || stm.tm_hour > 23)
		usage(BAD_DATE, argv[1]);
	stm.tm_min = atoi(minute);
	if (stm.tm_min < 0 || stm.tm_min > 59)
		usage(BAD_DATE, argv[1]);
	stm.tm_sec = atoi(second);
	if (stm.tm_sec < 0 || stm.tm_sec > 59)
		usage(BAD_DATE, argv[1]);
/*
 *	Convert time to seconds since The Epoch.
 */
	if ((new_time = mktime(&stm)) == -1)
		usage(BAD_DATE, argv[1]);
/*
 *	Don't allow adjustment of time by more than MAX_ADJ seconds.
 */
	if (abs(time(NULL) - new_time) > MAX_ADJ) {
		(void) fprintf(
			stderr,
			"%s: will not adjust time by more than %d seconds\n",
			progname, MAX_ADJ
		);
		exit(TOO_LARGE);
	}
/*
 *	Set the system time.
 */
	if (stime(&new_time) == -1) {
		extern int	errno;

		(void) fprintf(
			stderr, "%s: cannot set time: %s\n",
			progname, strerror(errno)
		);
		exit(BAD_STIME);
	}
/*
 *	Done.
 */
	exit(OK);
	/* NOTREACHED */
}
-- 
      -m------- Michael Henning                 +61 75 950255
    ---mmm----- Pyramid Technology              +61 75 722475 FAX
  -----mmmmm--- Research Park, Bond University  michi@ptcburp.ptcbu.oz.au
-------mmmmmmm- Gold Coast, Q 4229, AUSTRALIA   uunet!munnari!ptcburp.oz!michi
Technical Details
Message-ID: <1992Dec22.041148.24383@ptcburp.ptcbu.oz.au>
Path: arc1-ca.pugleaf.net!sparky!uunet!zaphod.mps.ohio-state.edu!darwin.sura.net!spool.mu.edu!umn.edu!math.fu-berlin.de!informatik.tu-muenchen.de!regent!monu1.cc.monash.edu.au!monu6!bruce.cs.monash.edu.au!munnari.oz.au!bunyip.cc.uq.oz.au!kirk!ptcburp!michi

Show Full Headers
Complete Article Headers
Newsgroups: aus.sources
Path: arc1-ca.pugleaf.net!sparky!uunet!zaphod.mps.ohio-state.edu!darwin.sura.net!spool.mu.edu!umn.edu!math.fu-berlin.de!informatik.tu-muenchen.de!regent!monu1.cc.monash.edu.au!monu6!bruce.cs.monash.edu.au!munnari.oz.au!bunyip.cc.uq.oz.au!kirk!ptcburp!michi
From: michi@ptcburp.ptcbu.oz.au (Michael Henning)
Subject: AOTC atomic clock
Message-ID: <1992Dec22.041148.24383@ptcburp.ptcbu.oz.au>
Summary: Code to set system date automatically
Keywords: AOTC, time, date, system time, clock
Organization: Pyramid Technology Corporation
Distribution: aus
Date: Tue, 22 Dec 1992 04:11:48 GMT
Lines: 509