154359Sroberto/*
2182007Sroberto * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time
3182007Sroberto *	Services
454359Sroberto */
554359Sroberto#ifdef HAVE_CONFIG_H
654359Sroberto#include <config.h>
754359Sroberto#endif
854359Sroberto
9290001Sglebius#if defined(REFCLOCK) && defined(CLOCK_ACTS)
1054359Sroberto
1154359Sroberto#include "ntpd.h"
1254359Sroberto#include "ntp_io.h"
1354359Sroberto#include "ntp_unixtime.h"
1454359Sroberto#include "ntp_refclock.h"
1554359Sroberto#include "ntp_stdlib.h"
1654359Sroberto#include "ntp_control.h"
1754359Sroberto
1882498Sroberto#include <stdio.h>
1982498Sroberto#include <ctype.h>
2082498Sroberto#ifdef HAVE_SYS_IOCTL_H
2182498Sroberto# include <sys/ioctl.h>
2282498Sroberto#endif /* HAVE_SYS_IOCTL_H */
2382498Sroberto
24290001Sglebius#ifdef SYS_WINNT
25290001Sglebius#undef write	/* ports/winnt/include/config.h: #define write _write */
26290001Sglebiusextern int async_write(int, const void *, unsigned int);
27290001Sglebius#define write(fd, data, octets)	async_write(fd, data, octets)
28290001Sglebius#endif
29290001Sglebius
3054359Sroberto/*
31182007Sroberto * This driver supports the US (NIST, USNO) and European (PTB, NPL,
32182007Sroberto * etc.) modem time services, as well as Spectracom GPS and WWVB
33182007Sroberto * receivers connected via a modem. The driver periodically dials a
34182007Sroberto * number from a telephone list, receives the timecode data and
35182007Sroberto * calculates the local clock correction. It is designed primarily for
36182007Sroberto * use as backup when neither a radio clock nor connectivity to Internet
37182007Sroberto * time servers is available.
3854359Sroberto *
39182007Sroberto * This driver requires a modem with a Hayes-compatible command set and
40182007Sroberto * control over the modem data terminal ready (DTR) control line. The
41182007Sroberto * modem setup string is hard-coded in the driver and may require
42290001Sglebius * changes for nonstandard modems or special circumstances.
4354359Sroberto *
44290001Sglebius * When enabled, the calling program dials the first number in the
45290001Sglebius * phones file. If that call fails, it dials the second number and
46290001Sglebius * so on. The phone number is specified by the Hayes ATDT prefix
47290001Sglebius * followed by the number itself, including the long-distance prefix
48290001Sglebius * and delay code, if necessary. The calling program is enabled
49290001Sglebius * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval
50290001Sglebius * when no other synchronization sources are present, and (c) at each
51290001Sglebius * poll interval whether or not other synchronization sources are
52290001Sglebius * present. The calling program disconnects if (a) the called party
53290001Sglebius * is busy or does not answer, (b) the called party disconnects
54290001Sglebius * before a sufficient nuimber of timecodes have been received.
5554359Sroberto *
56182007Sroberto * The driver is transparent to each of the modem time services and
57182007Sroberto * Spectracom radios. It selects the parsing algorithm depending on the
58182007Sroberto * message length. There is some hazard should the message be corrupted.
59182007Sroberto * However, the data format is checked carefully and only if all checks
60182007Sroberto * succeed is the message accepted. Corrupted lines are discarded
61182007Sroberto * without complaint.
6254359Sroberto *
63182007Sroberto * Fudge controls
6454359Sroberto *
65182007Sroberto * flag1	force a call in manual mode
66182007Sroberto * flag2	enable port locking (not verified)
67290001Sglebius * flag3	not used
68182007Sroberto * flag4	not used
6954359Sroberto *
70182007Sroberto * time1	offset adjustment (s)
7154359Sroberto *
72290001Sglebius * Ordinarily, the serial port is connected to a modem and the phones
73290001Sglebius * list is defined. If no phones list is defined, the port can be
74290001Sglebius * connected directly to a device or another computer. In this case the
75290001Sglebius * driver will send a single character 'T' at each poll event. If
76290001Sglebius * fudge flag2 is enabled, port locking allows the modem to be shared
77290001Sglebius * when not in use by this driver.
7854359Sroberto */
7954359Sroberto/*
80182007Sroberto * National Institute of Science and Technology (NIST)
8154359Sroberto *
82182007Sroberto * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii)
8354359Sroberto *
84182007Sroberto * Data Format
85182007Sroberto *
8654359Sroberto * National Institute of Standards and Technology
8754359Sroberto * Telephone Time Service, Generator 3B
8854359Sroberto * Enter question mark "?" for HELP
8954359Sroberto *                         D  L D
9054359Sroberto *  MJD  YR MO DA H  M  S  ST S UT1 msADV        <OTM>
91182007Sroberto * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF>
92182007Sroberto * ...
9354359Sroberto *
94182007Sroberto * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is
95182007Sroberto * the on-time markers echoed by the driver and used by NIST to measure
96290001Sglebius * and correct for the propagation delay. Note: the ACTS timecode has
97290001Sglebius * recently been changed to eliminate the * on-time indicator. The
98290001Sglebius * reason for this and the long term implications are not clear.
9954359Sroberto *
100182007Sroberto * US Naval Observatory (USNO)
10154359Sroberto *
102182007Sroberto * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO)
10354359Sroberto *
104182007Sroberto * Data Format (two lines, repeating at one-second intervals)
10554359Sroberto *
106182007Sroberto * jjjjj nnn hhmmss UTC<CR><LF>
107182007Sroberto * *<CR><LF>
10854359Sroberto *
109182007Sroberto * jjjjj	modified Julian day number (not used)
110182007Sroberto * nnn		day of year
111182007Sroberto * hhmmss	second of day
112182007Sroberto * *		on-time marker for previous timecode
113182007Sroberto * ...
11454359Sroberto *
115182007Sroberto * USNO does not correct for the propagation delay. A fudge time1 of
116182007Sroberto * about .06 s is advisable.
11754359Sroberto *
118182007Sroberto * European Services (PTB, NPL, etc.)
11954359Sroberto *
120182007Sroberto * PTB: +49 531 512038 (Germany)
121182007Sroberto * NPL: 0906 851 6333 (UK only)
12254359Sroberto *
123182007Sroberto * Data format (see the documentation for phone numbers and formats.)
12454359Sroberto *
125182007Sroberto * 1995-01-23 20:58:51 MEZ  10402303260219950123195849740+40000500<CR><LF>
12654359Sroberto *
127182007Sroberto * Spectracom GPS and WWVB Receivers
12854359Sroberto *
129182007Sroberto * If a modem is connected to a Spectracom receiver, this driver will
130182007Sroberto * call it up and retrieve the time in one of two formats. As this
131182007Sroberto * driver does not send anything, the radio will have to either be
132182007Sroberto * configured in continuous mode or be polled by another local driver.
13354359Sroberto */
13454359Sroberto/*
13554359Sroberto * Interface definitions
13654359Sroberto */
137182007Sroberto#define	DEVICE		"/dev/acts%d" /* device name and unit */
138290001Sglebius#define	SPEED232	B19200	/* uart speed (19200 bps) */
13954359Sroberto#define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
140290001Sglebius#define LOCKFILE	"/var/spool/lock/LCK..cua%d"
141182007Sroberto#define DESCRIPTION	"Automated Computer Time Service" /* WRU */
142182007Sroberto#define REFID		"NONE"	/* default reference ID */
143182007Sroberto#define MSGCNT		20	/* max message count */
144290001Sglebius#define	MAXPHONE	10	/* max number of phone numbers */
145182007Sroberto
146182007Sroberto/*
147290001Sglebius * Calling program modes (mode)
148182007Sroberto */
149290001Sglebius#define MODE_BACKUP	0	/* backup mode */
150290001Sglebius#define MODE_AUTO	1	/* automatic mode */
15154359Sroberto#define MODE_MANUAL	2	/* manual mode */
15254359Sroberto
153182007Sroberto/*
154290001Sglebius * Service identifiers (message length)
155182007Sroberto */
156182007Sroberto#define REFACTS		"NIST"	/* NIST reference ID */
157290001Sglebius#define LENACTS		50	/* NIST format A */
158182007Sroberto#define REFUSNO		"USNO"	/* USNO reference ID */
159182007Sroberto#define LENUSNO		20	/* USNO */
160182007Sroberto#define REFPTB		"PTB\0"	/* PTB/NPL reference ID */
161182007Sroberto#define LENPTB		78	/* PTB/NPL format */
162182007Sroberto#define REFWWVB		"WWVB"	/* WWVB reference ID */
163182007Sroberto#define	LENWWVB0	22	/* WWVB format 0 */
164182007Sroberto#define	LENWWVB2	24	/* WWVB format 2 */
165182007Sroberto#define LF		0x0a	/* ASCII LF */
16654359Sroberto
16754359Sroberto/*
168290001Sglebius * Modem setup strings. These may have to be changed for
169290001Sglebius * some modems.
17054359Sroberto *
17154359Sroberto * AT	command prefix
172182007Sroberto * B1	US answer tone
173182007Sroberto * &C0	disable carrier detect
17454359Sroberto * &D2	hang up and return to command mode on DTR transition
17554359Sroberto * E0	modem command echo disabled
176290001Sglebius * L1	set modem speaker volume to low level
177182007Sroberto * M1	speaker enabled until carrier detect
17854359Sroberto * Q0	return result codes
17954359Sroberto * V1	return result codes as English words
180290001Sglebius * Y1	enable long-space disconnect
18154359Sroberto */
182290001Sglebiusconst char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1";
183290001Sglebiusconst char *modem_setup = def_modem_setup;
18454359Sroberto
18554359Sroberto/*
186182007Sroberto * Timeouts (all in seconds)
18754359Sroberto */
188182007Sroberto#define SETUP		3	/* setup timeout */
189290001Sglebius#define	REDIAL		30	/* redial timeout */
190182007Sroberto#define ANSWER		60	/* answer timeout */
191290001Sglebius#define TIMECODE	60	/* message timeout */
192290001Sglebius#define	MAXCODE		20	/* max timecodes */
19354359Sroberto
19454359Sroberto/*
195182007Sroberto * State machine codes
19654359Sroberto */
197290001Sglebiustypedef enum {
198290001Sglebius	S_IDLE,			/* wait for poll */
199290001Sglebius	S_SETUP,		/* send modem setup */
200290001Sglebius	S_CONNECT,		/* wait for answer */
201290001Sglebius	S_MSG			/* wait for timecode */
202290001Sglebius} teModemState;
20354359Sroberto
20454359Sroberto/*
20554359Sroberto * Unit control structure
20654359Sroberto */
20754359Srobertostruct actsunit {
208182007Sroberto	int	unit;		/* unit number */
20954359Sroberto	int	state;		/* the first one was Delaware */
210182007Sroberto	int	timer;		/* timeout counter */
211182007Sroberto	int	retry;		/* retry index */
212182007Sroberto	int	msgcnt;		/* count of messages received */
213182007Sroberto	l_fp	tstamp;		/* on-time timestamp */
214290001Sglebius	char	*bufptr;	/* next incoming char stored here */
215290001Sglebius	char	buf[BMAX];	/* bufptr roams within buf[] */
21654359Sroberto};
21754359Sroberto
21854359Sroberto/*
21954359Sroberto * Function prototypes
22054359Sroberto */
221290001Sglebiusstatic	int	acts_start	(int, struct peer *);
222290001Sglebiusstatic	void	acts_shutdown	(int, struct peer *);
223290001Sglebiusstatic	void	acts_receive	(struct recvbuf *);
224290001Sglebiusstatic	void	acts_message	(struct peer *, const char *);
225290001Sglebiusstatic	void	acts_timecode	(struct peer *, const char *);
226290001Sglebiusstatic	void	acts_poll	(int, struct peer *);
227290001Sglebiusstatic	void	acts_timeout	(struct peer *, teModemState);
228290001Sglebiusstatic	void	acts_timer	(int, struct peer *);
229290001Sglebiusstatic	void	acts_close	(struct peer *);
23054359Sroberto
23154359Sroberto/*
23254359Sroberto * Transfer vector (conditional structure name)
23354359Sroberto */
234290001Sglebiusstruct refclock refclock_acts = {
23554359Sroberto	acts_start,		/* start up driver */
23654359Sroberto	acts_shutdown,		/* shut down driver */
23754359Sroberto	acts_poll,		/* transmit poll message */
238182007Sroberto	noentry,		/* not used */
239182007Sroberto	noentry,		/* not used */
240182007Sroberto	noentry,		/* not used */
241182007Sroberto	acts_timer		/* housekeeping timer */
24254359Sroberto};
24354359Sroberto
24454359Sroberto/*
245182007Sroberto * Initialize data for processing
24654359Sroberto */
24754359Srobertostatic int
248290001Sglebiusacts_start(
249182007Sroberto	int	unit,
25054359Sroberto	struct peer *peer
25154359Sroberto	)
25254359Sroberto{
253182007Sroberto	struct actsunit *up;
25454359Sroberto	struct refclockproc *pp;
255290001Sglebius	const char *setup;
25654359Sroberto
25754359Sroberto	/*
258182007Sroberto	 * Allocate and initialize unit structure
25954359Sroberto	 */
260290001Sglebius	up = emalloc_zero(sizeof(struct actsunit));
261182007Sroberto	up->unit = unit;
26254359Sroberto	pp = peer->procptr;
263290001Sglebius	pp->unitptr = up;
26454359Sroberto	pp->io.clock_recv = acts_receive;
265290001Sglebius	pp->io.srcclock = peer;
26654359Sroberto	pp->io.datalen = 0;
267290001Sglebius	pp->io.fd = -1;
26854359Sroberto
26954359Sroberto	/*
27054359Sroberto	 * Initialize miscellaneous variables
27154359Sroberto	 */
27254359Sroberto	peer->precision = PRECISION;
27354359Sroberto	pp->clockdesc = DESCRIPTION;
274290001Sglebius	memcpy(&pp->refid, REFID, 4);
27554359Sroberto	peer->sstclktype = CTL_SST_TS_TELEPHONE;
276290001Sglebius	up->bufptr = up->buf;
277290001Sglebius	if (def_modem_setup == modem_setup) {
278290001Sglebius		setup = get_ext_sys_var("modemsetup");
279290001Sglebius		if (setup != NULL)
280290001Sglebius			modem_setup = estrdup(setup);
281290001Sglebius	}
282290001Sglebius
28354359Sroberto	return (1);
28454359Sroberto}
28554359Sroberto
28654359Sroberto
28754359Sroberto/*
28854359Sroberto * acts_shutdown - shut down the clock
28954359Sroberto */
29054359Srobertostatic void
291290001Sglebiusacts_shutdown(
292182007Sroberto	int	unit,
29354359Sroberto	struct peer *peer
29454359Sroberto	)
29554359Sroberto{
296182007Sroberto	struct actsunit *up;
29754359Sroberto	struct refclockproc *pp;
29854359Sroberto
299182007Sroberto	/*
300182007Sroberto	 * Warning: do this only when a call is not in progress.
301182007Sroberto	 */
30254359Sroberto	pp = peer->procptr;
303290001Sglebius	up = pp->unitptr;
304290001Sglebius	acts_close(peer);
30554359Sroberto	free(up);
30654359Sroberto}
30754359Sroberto
30854359Sroberto
30954359Sroberto/*
31054359Sroberto * acts_receive - receive data from the serial interface
31154359Sroberto */
31254359Srobertostatic void
313290001Sglebiusacts_receive(
31454359Sroberto	struct recvbuf *rbufp
31554359Sroberto	)
31654359Sroberto{
317182007Sroberto	struct actsunit *up;
31854359Sroberto	struct refclockproc *pp;
31954359Sroberto	struct peer *peer;
320290001Sglebius	char	tbuf[sizeof(up->buf)];
321290001Sglebius	char *	tptr;
322290001Sglebius	int	octets;
323182007Sroberto
32454359Sroberto	/*
325182007Sroberto	 * Initialize pointers and read the timecode and timestamp. Note
326182007Sroberto	 * we are in raw mode and victim of whatever the terminal
327182007Sroberto	 * interface kicks up; so, we have to reassemble messages from
328182007Sroberto	 * arbitrary fragments. Capture the timecode at the beginning of
329182007Sroberto	 * the message and at the '*' and '#' on-time characters.
33054359Sroberto	 */
331290001Sglebius	peer = rbufp->recv_peer;
33254359Sroberto	pp = peer->procptr;
333290001Sglebius	up = pp->unitptr;
334290001Sglebius	octets = sizeof(up->buf) - (up->bufptr - up->buf);
335290001Sglebius	refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec);
336182007Sroberto	for (tptr = tbuf; *tptr != '\0'; tptr++) {
337182007Sroberto		if (*tptr == LF) {
338290001Sglebius			if (up->bufptr == up->buf) {
339182007Sroberto				up->tstamp = pp->lastrec;
340182007Sroberto				continue;
341182007Sroberto			} else {
342182007Sroberto				*up->bufptr = '\0';
343290001Sglebius				up->bufptr = up->buf;
344290001Sglebius				acts_message(peer, up->buf);
345182007Sroberto			}
346290001Sglebius		} else if (!iscntrl((unsigned char)*tptr)) {
347182007Sroberto			*up->bufptr++ = *tptr;
348182007Sroberto			if (*tptr == '*' || *tptr == '#') {
349182007Sroberto				up->tstamp = pp->lastrec;
350290001Sglebius				if (write(pp->io.fd, tptr, 1) < 0)
351290001Sglebius					msyslog(LOG_ERR, "acts: write echo fails %m");
352182007Sroberto			}
353182007Sroberto		}
35454359Sroberto	}
355182007Sroberto}
356182007Sroberto
357182007Sroberto
358182007Sroberto/*
359182007Sroberto * acts_message - process message
360182007Sroberto */
361182007Srobertovoid
362182007Srobertoacts_message(
363290001Sglebius	struct peer *peer,
364290001Sglebius	const char *msg
365182007Sroberto	)
366182007Sroberto{
367182007Sroberto	struct actsunit *up;
368182007Sroberto	struct refclockproc *pp;
369290001Sglebius	char	tbuf[BMAX];
370290001Sglebius	int		dtr = TIOCM_DTR;
371182007Sroberto
372290001Sglebius	DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg));
373290001Sglebius
374182007Sroberto	/*
375182007Sroberto	 * What to do depends on the state and the first token in the
376290001Sglebius	 * message.
377182007Sroberto	 */
378182007Sroberto	pp = peer->procptr;
379290001Sglebius	up = pp->unitptr;
380290001Sglebius
381290001Sglebius	/*
382290001Sglebius	 * Extract the first token in the line.
383290001Sglebius	 */
384290001Sglebius	strlcpy(tbuf, msg, sizeof(tbuf));
385182007Sroberto	strtok(tbuf, " ");
386290001Sglebius	switch (up->state) {
38754359Sroberto
388182007Sroberto	/*
389182007Sroberto	 * We are waiting for the OK response to the modem setup
390290001Sglebius	 * command. When this happens, dial the number followed.
391290001Sglebius	 * If anything other than OK is received, just ignore it
392290001Sglebius	 * and wait for timeoue.
393182007Sroberto	 */
394290001Sglebius	case S_SETUP:
395182007Sroberto		if (strcmp(tbuf, "OK") != 0) {
396290001Sglebius			/*
397290001Sglebius			 * We disable echo with MODEM_SETUP's E0 but
398290001Sglebius			 * if the modem was previously E1, we will
399290001Sglebius			 * see MODEM_SETUP echoed before the OK/ERROR.
400290001Sglebius			 * Ignore it.
401290001Sglebius			 */
402290001Sglebius			if (!strcmp(tbuf, modem_setup))
403290001Sglebius				return;
404290001Sglebius			break;
405182007Sroberto		}
406290001Sglebius
407290001Sglebius		mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s",
408290001Sglebius			      up->retry, sys_phone[up->retry]);
409290001Sglebius		if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0)
410290001Sglebius			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m");
411290001Sglebius		if (write(pp->io.fd, sys_phone[up->retry],
412290001Sglebius		    strlen(sys_phone[up->retry])) < 0)
413290001Sglebius			msyslog(LOG_ERR, "acts: write DIAL fails %m");
414290001Sglebius		write(pp->io.fd, "\r", 1);
415290001Sglebius		up->retry++;
416290001Sglebius		up->state = S_CONNECT;
417290001Sglebius		up->timer = ANSWER;
41854359Sroberto		return;
41954359Sroberto
420182007Sroberto	/*
421290001Sglebius	 * We are waiting for the CONNECT response to the dial
422290001Sglebius	 * command. When this happens, listen for timecodes. If
423290001Sglebius	 * somthing other than CONNECT is received, like BUSY
424290001Sglebius	 * or NO CARRIER, abort the call.
425182007Sroberto	 */
426182007Sroberto	case S_CONNECT:
427290001Sglebius		if (strcmp(tbuf, "CONNECT") != 0)
428290001Sglebius			break;
429290001Sglebius
430290001Sglebius		report_event(PEVNT_CLOCK, peer, msg);
431290001Sglebius		up->state = S_MSG;
432290001Sglebius		up->timer = TIMECODE;
433290001Sglebius		return;
434290001Sglebius
435290001Sglebius	/*
436290001Sglebius	 * We are waiting for a timecode response. Pass it to
437290001Sglebius	 * the parser. If NO CARRIER is received, save the
438290001Sglebius	 * messages and abort the call.
439290001Sglebius	 */
440290001Sglebius	case S_MSG:
441290001Sglebius		if (strcmp(tbuf, "NO") == 0)
442290001Sglebius			report_event(PEVNT_CLOCK, peer, msg);
443290001Sglebius		if (up->msgcnt < MAXCODE)
444290001Sglebius			acts_timecode(peer, msg);
445290001Sglebius		else
446290001Sglebius			acts_timeout(peer, S_MSG);
447290001Sglebius		return;
448290001Sglebius	}
449290001Sglebius
450290001Sglebius	/*
451290001Sglebius	 * Other response. Tell us about it.
452290001Sglebius	 */
453290001Sglebius	report_event(PEVNT_CLOCK, peer, msg);
454290001Sglebius	acts_close(peer);
455290001Sglebius}
456290001Sglebius
457290001Sglebius
458290001Sglebius/*
459290001Sglebius * acts_timeout - called on timeout
460290001Sglebius */
461290001Sglebiusstatic void
462290001Sglebiusacts_timeout(
463290001Sglebius	struct peer *peer,
464290001Sglebius	teModemState	dstate
465290001Sglebius	)
466290001Sglebius{
467290001Sglebius	struct actsunit *up;
468290001Sglebius	struct refclockproc *pp;
469290001Sglebius	int	fd;
470290001Sglebius	int	rc;
471290001Sglebius	char	device[20];
472290001Sglebius	char	lockfile[128], pidbuf[8];
473290001Sglebius
474290001Sglebius	/*
475290001Sglebius	 * The state machine is driven by messages from the modem,
476290001Sglebius	 * when first started and at timeout.
477290001Sglebius	 */
478290001Sglebius	pp = peer->procptr;
479290001Sglebius	up = pp->unitptr;
480290001Sglebius	switch (dstate) {
481290001Sglebius
482290001Sglebius	/*
483290001Sglebius	 * System poll event. Lock the modem port, open the device
484290001Sglebius	 * and send the setup command.
485290001Sglebius	 */
486290001Sglebius	case S_IDLE:
487290001Sglebius		if (-1 != pp->io.fd)
488290001Sglebius			return;		/* port is already open */
489290001Sglebius
490290001Sglebius		/*
491290001Sglebius		 * Lock the modem port. If busy, retry later. Note: if
492290001Sglebius		 * something fails between here and the close, the lock
493290001Sglebius		 * file may not be removed.
494290001Sglebius		 */
495290001Sglebius		if (pp->sloppyclockflag & CLK_FLAG2) {
496290001Sglebius			snprintf(lockfile, sizeof(lockfile), LOCKFILE,
497290001Sglebius			    up->unit);
498290001Sglebius			fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL,
499290001Sglebius			    0644);
500290001Sglebius			if (fd < 0) {
501290001Sglebius				report_event(PEVNT_CLOCK, peer, "acts: port busy");
502290001Sglebius				return;
503290001Sglebius			}
504290001Sglebius			snprintf(pidbuf, sizeof(pidbuf), "%d\n",
505290001Sglebius			    (u_int)getpid());
506290001Sglebius			if (write(fd, pidbuf, strlen(pidbuf)) < 0)
507290001Sglebius				msyslog(LOG_ERR, "acts: write lock fails %m");
508290001Sglebius			close(fd);
509290001Sglebius		}
510290001Sglebius
511290001Sglebius		/*
512290001Sglebius		 * Open the device in raw mode and link the I/O.
513290001Sglebius		 */
514290001Sglebius		snprintf(device, sizeof(device), DEVICE,
515290001Sglebius		    up->unit);
516290001Sglebius		fd = refclock_open(device, SPEED232, LDISC_ACTS |
517290001Sglebius		    LDISC_RAW | LDISC_REMOTE);
518290001Sglebius		if (fd < 0) {
519290001Sglebius			msyslog(LOG_ERR, "acts: open fails %m");
52054359Sroberto			return;
52154359Sroberto		}
522290001Sglebius		pp->io.fd = fd;
523290001Sglebius		if (!io_addclock(&pp->io)) {
524290001Sglebius			msyslog(LOG_ERR, "acts: addclock fails");
525290001Sglebius			close(fd);
526290001Sglebius			pp->io.fd = -1;
527290001Sglebius			return;
528290001Sglebius		}
529290001Sglebius		up->msgcnt = 0;
530290001Sglebius		up->bufptr = up->buf;
531290001Sglebius
532290001Sglebius		/*
533290001Sglebius		 * If the port is directly connected to the device, skip
534290001Sglebius		 * the modem business and send 'T' for Spectrabum.
535290001Sglebius		 */
536290001Sglebius		if (sys_phone[up->retry] == NULL) {
537290001Sglebius			if (write(pp->io.fd, "T", 1) < 0)
538290001Sglebius				msyslog(LOG_ERR, "acts: write T fails %m");
539290001Sglebius			up->state = S_MSG;
540290001Sglebius			up->timer = TIMECODE;
541290001Sglebius			return;
542290001Sglebius		}
543290001Sglebius
544290001Sglebius		/*
545290001Sglebius		 * Initialize the modem. This works with Hayes-
546290001Sglebius		 * compatible modems.
547290001Sglebius		 */
548290001Sglebius		mprintf_event(PEVNT_CLOCK, peer, "SETUP %s",
549290001Sglebius			      modem_setup);
550290001Sglebius		rc = write(pp->io.fd, modem_setup, strlen(modem_setup));
551290001Sglebius		if (rc < 0)
552290001Sglebius			msyslog(LOG_ERR, "acts: write SETUP fails %m");
553290001Sglebius		write(pp->io.fd, "\r", 1);
554290001Sglebius		up->state = S_SETUP;
555290001Sglebius		up->timer = SETUP;
55654359Sroberto		return;
55754359Sroberto
558182007Sroberto	/*
559290001Sglebius	 * In SETUP state the modem did not respond OK to setup string.
560182007Sroberto	 */
561290001Sglebius	case S_SETUP:
562290001Sglebius		report_event(PEVNT_CLOCK, peer, "no modem");
563290001Sglebius		break;
564290001Sglebius
565290001Sglebius	/*
566290001Sglebius	 * In CONNECT state the call did not complete. Abort the call.
567290001Sglebius	 */
568290001Sglebius	case S_CONNECT:
569290001Sglebius		report_event(PEVNT_CLOCK, peer, "no answer");
570290001Sglebius		break;
571290001Sglebius
572290001Sglebius	/*
573290001Sglebius	 * In MSG states no further timecodes are expected. If any
574290001Sglebius	 * timecodes have arrived, update the clock. In any case,
575290001Sglebius	 * terminate the call.
576290001Sglebius	 */
577182007Sroberto	case S_MSG:
578290001Sglebius		if (up->msgcnt == 0) {
579290001Sglebius			report_event(PEVNT_CLOCK, peer, "no timecodes");
580290001Sglebius		} else {
581290001Sglebius			pp->lastref = pp->lastrec;
582290001Sglebius			record_clock_stats(&peer->srcadr, pp->a_lastcode);
583290001Sglebius			refclock_receive(peer);
584290001Sglebius		}
585182007Sroberto		break;
58654359Sroberto	}
587290001Sglebius	acts_close(peer);
588182007Sroberto}
58954359Sroberto
590290001Sglebius
591182007Sroberto/*
592290001Sglebius * acts_close - close and prepare for next call.
593290001Sglebius *
594290001Sglebius * In ClOSE state no further protocol actions are required
595290001Sglebius * other than to close and release the device and prepare to
596290001Sglebius * dial the next number if necessary.
597290001Sglebius */
598290001Sglebiusvoid
599290001Sglebiusacts_close(
600290001Sglebius	struct peer *peer
601290001Sglebius	)
602290001Sglebius{
603290001Sglebius	struct actsunit *up;
604290001Sglebius	struct refclockproc *pp;
605290001Sglebius	char	lockfile[128];
606290001Sglebius	int	dtr;
607290001Sglebius
608290001Sglebius	pp = peer->procptr;
609290001Sglebius	up = pp->unitptr;
610290001Sglebius	if (pp->io.fd != -1) {
611290001Sglebius		report_event(PEVNT_CLOCK, peer, "close");
612290001Sglebius		dtr = TIOCM_DTR;
613290001Sglebius		if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0)
614290001Sglebius			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m");
615290001Sglebius		io_closeclock(&pp->io);
616290001Sglebius		pp->io.fd = -1;
617290001Sglebius	}
618290001Sglebius	if (pp->sloppyclockflag & CLK_FLAG2) {
619290001Sglebius		snprintf(lockfile, sizeof(lockfile),
620290001Sglebius		    LOCKFILE, up->unit);
621290001Sglebius		unlink(lockfile);
622290001Sglebius	}
623290001Sglebius	if (up->msgcnt == 0 && up->retry > 0) {
624290001Sglebius		if (sys_phone[up->retry] != NULL) {
625290001Sglebius			up->state = S_IDLE;
626290001Sglebius			up->timer = REDIAL;
627290001Sglebius			return;
628290001Sglebius		}
629290001Sglebius	}
630290001Sglebius	up->state = S_IDLE;
631290001Sglebius	up->timer = 0;
632290001Sglebius}
633290001Sglebius
634290001Sglebius
635290001Sglebius/*
636290001Sglebius * acts_poll - called by the transmit routine
637290001Sglebius */
638290001Sglebiusstatic void
639290001Sglebiusacts_poll(
640290001Sglebius	int	unit,
641290001Sglebius	struct peer *peer
642290001Sglebius	)
643290001Sglebius{
644290001Sglebius	struct actsunit *up;
645290001Sglebius	struct refclockproc *pp;
646290001Sglebius
647290001Sglebius	/*
648290001Sglebius	 * This routine is called at every system poll. All it does is
649290001Sglebius	 * set flag1 under certain conditions. The real work is done by
650290001Sglebius	 * the timeout routine and state machine.
651290001Sglebius	 */
652290001Sglebius	pp = peer->procptr;
653290001Sglebius	up = pp->unitptr;
654290001Sglebius	switch (peer->ttl) {
655290001Sglebius
656290001Sglebius	/*
657290001Sglebius	 * In manual mode the calling program is activated by the ntpdc
658290001Sglebius	 * program using the enable flag (fudge flag1), either manually
659290001Sglebius	 * or by a cron job.
660290001Sglebius	 */
661290001Sglebius	case MODE_MANUAL:
662290001Sglebius		return;
663290001Sglebius
664290001Sglebius	/*
665290001Sglebius	 * In automatic mode the calling program runs continuously at
666290001Sglebius	 * intervals determined by the poll event or specified timeout.
667290001Sglebius	 */
668290001Sglebius	case MODE_AUTO:
669290001Sglebius		break;
670290001Sglebius
671290001Sglebius	/*
672290001Sglebius	 * In backup mode the calling program runs continuously as long
673290001Sglebius	 * as either no peers are available or this peer is selected.
674290001Sglebius	 */
675290001Sglebius	case MODE_BACKUP:
676290001Sglebius		if (!(sys_peer == NULL || sys_peer == peer))
677290001Sglebius			return;
678290001Sglebius
679290001Sglebius		break;
680290001Sglebius	}
681290001Sglebius	pp->polls++;
682290001Sglebius	if (S_IDLE == up->state) {
683290001Sglebius		up->retry = 0;
684290001Sglebius		acts_timeout(peer, S_IDLE);
685290001Sglebius	}
686290001Sglebius}
687290001Sglebius
688290001Sglebius
689290001Sglebius/*
690290001Sglebius * acts_timer - called at one-second intervals
691290001Sglebius */
692290001Sglebiusstatic void
693290001Sglebiusacts_timer(
694290001Sglebius	int	unit,
695290001Sglebius	struct peer *peer
696290001Sglebius	)
697290001Sglebius{
698290001Sglebius	struct actsunit *up;
699290001Sglebius	struct refclockproc *pp;
700290001Sglebius
701290001Sglebius	/*
702290001Sglebius	 * This routine implments a timeout which runs for a programmed
703290001Sglebius	 * interval. The counter is initialized by the state machine and
704290001Sglebius	 * counts down to zero. Upon reaching zero, the state machine is
705290001Sglebius	 * called. If flag1 is set while timer is zero, force a call.
706290001Sglebius	 */
707290001Sglebius	pp = peer->procptr;
708290001Sglebius	up = pp->unitptr;
709290001Sglebius	if (up->timer == 0) {
710290001Sglebius		if (pp->sloppyclockflag & CLK_FLAG1) {
711290001Sglebius			pp->sloppyclockflag &= ~CLK_FLAG1;
712290001Sglebius			acts_timeout(peer, S_IDLE);
713290001Sglebius		}
714290001Sglebius	} else {
715290001Sglebius		up->timer--;
716290001Sglebius		if (up->timer == 0)
717290001Sglebius			acts_timeout(peer, up->state);
718290001Sglebius	}
719290001Sglebius}
720290001Sglebius
721290001Sglebius/*
722182007Sroberto * acts_timecode - identify the service and parse the timecode message
723182007Sroberto */
724182007Srobertovoid
725182007Srobertoacts_timecode(
726290001Sglebius	struct peer *	peer,	/* peer structure pointer */
727290001Sglebius	const char *	str	/* timecode string */
728182007Sroberto	)
729182007Sroberto{
730182007Sroberto	struct actsunit *up;
731182007Sroberto	struct refclockproc *pp;
732182007Sroberto	int	day;		/* day of the month */
733182007Sroberto	int	month;		/* month of the year */
734182007Sroberto	u_long	mjd;		/* Modified Julian Day */
735182007Sroberto	double	dut1;		/* DUT adjustment */
736182007Sroberto
737182007Sroberto	u_int	dst;		/* ACTS daylight/standard time */
738182007Sroberto	u_int	leap;		/* ACTS leap indicator */
739182007Sroberto	double	msADV;		/* ACTS transmit advance (ms) */
740182007Sroberto	char	utc[10];	/* ACTS timescale */
741182007Sroberto	char	flag;		/* ACTS on-time character (* or #) */
742182007Sroberto
743182007Sroberto	char	synchar;	/* WWVB synchronized indicator */
744182007Sroberto	char	qualchar;	/* WWVB quality indicator */
745182007Sroberto	char	leapchar;	/* WWVB leap indicator */
746182007Sroberto	char	dstchar;	/* WWVB daylight/savings indicator */
747182007Sroberto	int	tz;		/* WWVB timezone */
748182007Sroberto
749290001Sglebius	int	leapmonth;	/* PTB/NPL month of leap */
750182007Sroberto	char	leapdir;	/* PTB/NPL leap direction */
751182007Sroberto
75254359Sroberto	/*
753182007Sroberto	 * The parser selects the modem format based on the message
754182007Sroberto	 * length. Since the data are checked carefully, occasional
755182007Sroberto	 * errors due noise are forgivable.
75654359Sroberto	 */
757182007Sroberto	pp = peer->procptr;
758290001Sglebius	up = pp->unitptr;
759182007Sroberto	pp->nsec = 0;
760290001Sglebius	switch (strlen(str)) {
76154359Sroberto
76254359Sroberto	/*
763182007Sroberto	 * For USNO format on-time character '*', which is on a line by
764182007Sroberto	 * itself. Be sure a timecode has been received.
76554359Sroberto	 */
766182007Sroberto	case 1:
767182007Sroberto		if (*str == '*' && up->msgcnt > 0)
768182007Sroberto			break;
769182007Sroberto
77054359Sroberto		return;
771182007Sroberto
77254359Sroberto	/*
773290001Sglebius	 * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa
774290001Sglebius	 * UTC(NIST) *".
77554359Sroberto	 */
776182007Sroberto	case LENACTS:
777182007Sroberto		if (sscanf(str,
778182007Sroberto		    "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c",
779182007Sroberto		    &mjd, &pp->year, &month, &day, &pp->hour,
780182007Sroberto		    &pp->minute, &pp->second, &dst, &leap, &dut1,
781182007Sroberto		    &msADV, utc, &flag) != 13) {
782182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
783182007Sroberto			return;
784182007Sroberto		}
785182007Sroberto		pp->day = ymd2yd(pp->year, month, day);
786182007Sroberto		pp->leap = LEAP_NOWARNING;
787182007Sroberto		if (leap == 1)
788290001Sglebius			pp->leap = LEAP_ADDSECOND;
789290001Sglebius		else if (leap == 2)
790290001Sglebius			pp->leap = LEAP_DELSECOND;
791182007Sroberto		memcpy(&pp->refid, REFACTS, 4);
792182007Sroberto		up->msgcnt++;
793290001Sglebius		if (flag != '#' && up->msgcnt < 10)
794290001Sglebius			return;
795290001Sglebius
796182007Sroberto		break;
797182007Sroberto
79854359Sroberto	/*
799182007Sroberto	 * USNO format: "jjjjj nnn hhmmss UTC"
80054359Sroberto	 */
801182007Sroberto	case LENUSNO:
802182007Sroberto		if (sscanf(str, "%5ld %3d %2d%2d%2d %3s",
803182007Sroberto		    &mjd, &pp->day, &pp->hour, &pp->minute,
804182007Sroberto		    &pp->second, utc) != 6) {
805182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
806182007Sroberto			return;
807182007Sroberto		}
80854359Sroberto
809182007Sroberto		/*
810182007Sroberto		 * Wait for the on-time character, which follows in a
811182007Sroberto		 * separate message. There is no provision for leap
812182007Sroberto		 * warning.
813182007Sroberto		 */
814182007Sroberto		pp->leap = LEAP_NOWARNING;
815182007Sroberto		memcpy(&pp->refid, REFUSNO, 4);
816182007Sroberto		up->msgcnt++;
817290001Sglebius		break;
818182007Sroberto
81954359Sroberto	/*
820182007Sroberto	 * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ"
82154359Sroberto	 */
822182007Sroberto	case LENPTB:
823182007Sroberto		if (sscanf(str,
824182007Sroberto		    "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c",
825182007Sroberto		    &pp->second, &pp->year, &month, &day, &pp->hour,
826182007Sroberto		    &pp->minute, &mjd, &dut1, &leapdir, &leapmonth,
827182007Sroberto		    &msADV, &flag) != 12) {
828182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
829182007Sroberto			return;
830182007Sroberto		}
831182007Sroberto		pp->leap = LEAP_NOWARNING;
832182007Sroberto		if (leapmonth == month) {
833182007Sroberto			if (leapdir == '+')
834290001Sglebius				pp->leap = LEAP_ADDSECOND;
835182007Sroberto			else if (leapdir == '-')
836290001Sglebius				pp->leap = LEAP_DELSECOND;
837182007Sroberto		}
838182007Sroberto		pp->day = ymd2yd(pp->year, month, day);
839182007Sroberto		memcpy(&pp->refid, REFPTB, 4);
840182007Sroberto		up->msgcnt++;
841182007Sroberto		break;
84282498Sroberto
843182007Sroberto
84482498Sroberto	/*
845182007Sroberto	 * WWVB format 0: "I  ddd hh:mm:ss DTZ=nn"
84682498Sroberto	 */
847182007Sroberto	case LENWWVB0:
848182007Sroberto		if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d",
849182007Sroberto		    &synchar, &pp->day, &pp->hour, &pp->minute,
850182007Sroberto		    &pp->second, &dstchar, &tz) != 7) {
851182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
85254359Sroberto			return;
85354359Sroberto		}
854182007Sroberto		pp->leap = LEAP_NOWARNING;
855182007Sroberto		if (synchar != ' ')
856182007Sroberto			pp->leap = LEAP_NOTINSYNC;
857182007Sroberto		memcpy(&pp->refid, REFWWVB, 4);
858182007Sroberto		up->msgcnt++;
859182007Sroberto		break;
860182007Sroberto
861182007Sroberto	/*
862182007Sroberto	 * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD"
863182007Sroberto	 */
864182007Sroberto	case LENWWVB2:
865182007Sroberto		if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c",
866182007Sroberto		    &synchar, &qualchar, &pp->year, &pp->day,
867182007Sroberto		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
868182007Sroberto		    &dstchar, &leapchar, &dstchar) != 11) {
869182007Sroberto			refclock_report(peer, CEVNT_BADREPLY);
87054359Sroberto			return;
87154359Sroberto		}
872182007Sroberto		pp->nsec *= 1000000;
873182007Sroberto		pp->leap = LEAP_NOWARNING;
874182007Sroberto		if (synchar != ' ')
875182007Sroberto			pp->leap = LEAP_NOTINSYNC;
876182007Sroberto		else if (leapchar == 'L')
877182007Sroberto			pp->leap = LEAP_ADDSECOND;
878182007Sroberto		memcpy(&pp->refid, REFWWVB, 4);
879182007Sroberto		up->msgcnt++;
880182007Sroberto		break;
88154359Sroberto
88254359Sroberto	/*
883182007Sroberto	 * None of the above. Just forget about it and wait for the next
884182007Sroberto	 * message or timeout.
88554359Sroberto	 */
886182007Sroberto	default:
887182007Sroberto		return;
88854359Sroberto	}
88954359Sroberto
89054359Sroberto	/*
891182007Sroberto	 * We have a valid timecode. The fudge time1 value is added to
892182007Sroberto	 * each sample by the main line routines. Note that in current
893182007Sroberto	 * telephone networks the propatation time can be different for
894182007Sroberto	 * each call and can reach 200 ms for some calls.
89554359Sroberto	 */
896182007Sroberto	peer->refid = pp->refid;
897182007Sroberto	pp->lastrec = up->tstamp;
898290001Sglebius	if (up->msgcnt == 0)
899290001Sglebius		return;
900290001Sglebius
901290001Sglebius	strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode));
902290001Sglebius	pp->lencode = strlen(pp->a_lastcode);
903182007Sroberto	if (!refclock_process(pp)) {
904182007Sroberto		refclock_report(peer, CEVNT_BADTIME);
905182007Sroberto		return;
906290001Sglebius	}
907132451Sroberto	pp->lastref = pp->lastrec;
90854359Sroberto}
90954359Sroberto#else
91054359Srobertoint refclock_acts_bs;
91154359Sroberto#endif /* REFCLOCK */
912