aboutsummaryrefslogtreecommitdiff
path: root/contrib/ntp/ntpd/refclock_usno.c
blob: 30a13300bac5d004fd80ded71dfcba54fedbc3ed (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
/*
 * refclock_usno - clock driver for the Naval Observatory dialup
 * Michael Shields <shields@tembel.org> 1995/02/25
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_USNO)

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"
#include "ntp_control.h"

#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */

/*
 * This driver supports the Naval Observatory dialup at +1 202 653 0351.
 * It is a hacked-up version of the ACTS driver.
 *
 * This driver does not support the `phone' configuration because that
 * is needlessly global; it would clash with the ACTS driver.
 *
 * The Naval Observatory does not support the echo-delay measurement scheme.
 *
 * However, this driver *does* support UUCP port locking, allowing the
 * line to be shared with other processes when not actually dialing
 * for time.
 */

/*
 * Interface definitions
 */

#define	DEVICE		"/dev/cua%d" /* device name and unit */
#define LOCKFILE	"/var/lock/LCK..cua%d"
/* #define LOCKFILE	"/usr/spool/uucp/LCK..cua%d" */

#define PHONE		"atdt 202 653 0351"
/* #define PHONE	"atdt 1 202 653 0351" */

#define	SPEED232	B1200	/* uart speed (1200 cowardly baud) */
#define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
#define	REFID		"USNO"	/* reference ID */
#define	DESCRIPTION	"Naval Observatory dialup"

#define MODE_AUTO	0	/* automatic mode */
#define MODE_BACKUP	1	/* backup mode */
#define MODE_MANUAL	2	/* manual mode */

#define MSGCNT		10	/* we need this many time messages */
#define SMAX		80	/* max token string length */
#define LENCODE		20	/* length of valid timecode string */
#define USNO_MINPOLL	10	/* log2 min poll interval (1024 s) */
#define USNO_MAXPOLL	14	/* log2 max poll interval (16384 s) */
#define MAXOUTAGE	3600	/* max before USNO kicks in (s) */

/*
 * Modem control strings. These may have to be changed for some modems.
 *
 * AT	command prefix
 * B1	initiate call negotiation using Bell 212A
 * &C1	enable carrier detect
 * &D2	hang up and return to command mode on DTR transition
 * E0	modem command echo disabled
 * l1	set modem speaker volume to low level
 * M1	speaker enabled untill carrier detect
 * Q0	return result codes
 * V1	return result codes as English words
 */
#define MODEM_SETUP	"ATB1&C1&D2E0L1M1Q0V1" /* modem setup */
#define MODEM_HANGUP	"ATH"	/* modem disconnect */

/*
 * Timeouts
 */
#define IDLE		60	/* idle timeout (s) */
#define WAIT		2	/* wait timeout (s) */
#define ANSWER		30	/* answer timeout (s) */
#define CONNECT		10	/* connect timeout (s) */
#define TIMECODE	(MSGCNT+16)	/* timecode timeout (s) */

/*
 * Unit control structure
 */
struct usnounit {
	int	pollcnt;	/* poll message counter */

	int	state;		/* the first one was Delaware */
	int	run;		/* call program run switch */
	int	msgcnt;		/* count of time messages received */
	long	redial;		/* interval to next automatic call */
	int	unit;		/* unit number (= port) */
};

/*
 * Function prototypes
 */
static	int	usno_start	P((int, struct peer *));
static	void	usno_shutdown	P((int, struct peer *));
static	void	usno_poll	P((int, struct peer *));
static	void	usno_disc	P((struct peer *));
#if 0
static	void	usno_timeout	P((struct peer *));
static	void	usno_receive	P((struct recvbuf *));
static	int	usno_write	P((struct peer *, const char *));
#endif /* 0 */

/*
 * Transfer vector
 */
struct	refclock refclock_usno = {
	usno_start,		/* start up driver */
	usno_shutdown,		/* shut down driver */
	usno_poll,		/* transmit poll message */
	noentry,		/* not used (usno_control) */
	noentry,		/* not used (usno_init) */
	noentry,		/* not used (usno_buginfo) */
	NOFLAGS			/* not used */
};


/*
 * usno_start - open the devices and initialize data for processing
 */
static int
usno_start(
	int unit,
	struct peer *peer
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;

	/*
	 * Initialize miscellaneous variables
	 */
	pp = peer->procptr;
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy((char *)&pp->refid, REFID, 4);
	peer->minpoll = USNO_MINPOLL;
	peer->maxpoll = USNO_MAXPOLL;
	peer->sstclktype = CTL_SST_TS_TELEPHONE;

	/*
	 * Allocate and initialize unit structure
	 */
	if (!(up = (struct usnounit *)
	      emalloc(sizeof(struct usnounit))))
	    return (0);
	memset((char *)up, 0, sizeof(struct usnounit));
	up->unit = unit;
	pp->unitptr = (caddr_t)up;

	/*
	 * Set up the driver timeout
	 */
	peer->nextdate = current_time + WAIT;
	return (1);
}


/*
 * usno_shutdown - shut down the clock
 */
static void
usno_shutdown(
	int unit,
	struct peer *peer
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;

#ifdef DEBUG
	if (debug)
	    printf("usno: clock %s shutting down\n", ntoa(&peer->srcadr));
#endif
	pp = peer->procptr;
	up = (struct usnounit *)pp->unitptr;
	usno_disc(peer);
	free(up);
}


#if 0
/*
 * usno_receive - receive data from the serial interface
 */
static void
usno_receive(
	struct recvbuf *rbufp
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;
	struct peer *peer;
	char str[SMAX];
	u_long mjd;		/* Modified Julian Day */
	static int day, hour, minute, second;

	/*
	 * Initialize pointers and read the timecode and timestamp. If
	 * the OK modem status code, leave it where folks can find it.
	 */
	peer = (struct peer *)rbufp->recv_srcclock;
	pp = peer->procptr;
	up = (struct usnounit *)pp->unitptr;
	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX,
				     &pp->lastrec);
	if (pp->lencode == 0) {
		if (strcmp(pp->a_lastcode, "OK") == 0)
		    pp->lencode = 2;
		return;
	}
#ifdef DEBUG
	if (debug)
	    printf("usno: timecode %d %s\n", pp->lencode,
		   pp->a_lastcode);
#endif

	switch (up->state) {

	    case 0:

		/*
		 * State 0. We are not expecting anything. Probably
		 * modem disconnect noise. Go back to sleep.
		 */
		return;

	    case 1:

		/*
		 * State 1. We are about to dial. Just drop it.
		 */
		return;

	    case 2:

		/*
		 * State 2. We are waiting for the call to be answered.
		 * All we care about here is CONNECT as the first token
		 * in the string. If the modem signals BUSY, ERROR, NO
		 * ANSWER, NO CARRIER or NO DIALTONE, we immediately
		 * hang up the phone. If CONNECT doesn't happen after
		 * ANSWER seconds, hang up the phone. If everything is
		 * okay, start the connect timeout and slide into state
		 * 3.
		 */
		(void)strncpy(str, strtok(pp->a_lastcode, " "), SMAX);
		if (strcmp(str, "BUSY") == 0 || strcmp(str, "ERROR") ==
		    0 || strcmp(str, "NO") == 0) {
			NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
				msyslog(LOG_NOTICE,
					"clock %s USNO modem status %s",
					ntoa(&peer->srcadr), pp->a_lastcode);
			usno_disc(peer);
		} else if (strcmp(str, "CONNECT") == 0) {
			peer->nextdate = current_time + CONNECT;
			up->msgcnt = 0;
			up->state++;
		} else {
			NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
				msyslog(LOG_WARNING,
					"clock %s USNO unknown modem status %s",
					ntoa(&peer->srcadr), pp->a_lastcode);
		}
		return;

	    case 3:

		/*
		 * State 3. The call has been answered and we are
		 * waiting for the first message. If this doesn't
		 * happen within the timecode timeout, hang up the
		 * phone. We probably got a wrong number or they are
		 * down.
		 */
		peer->nextdate = current_time + TIMECODE;
		up->state++;
		return;

	    case 4:

		/*
		 * State 4. We are reading a timecode.  It's an actual
		 * timecode, or it's the `*' OTM.
		 *
		 * jjjjj nnn hhmmss UTC
		 */
		if (pp->lencode == LENCODE) {
			if (sscanf(pp->a_lastcode, "%5ld %3d %2d%2d%2d UTC",
				   &mjd, &day, &hour, &minute, &second) != 5) {
#ifdef DEBUG
				if (debug)
				    printf("usno: bad timecode format\n");
#endif
				refclock_report(peer, CEVNT_BADREPLY);
			} else
			    up->msgcnt++;
			return;
		} else if (pp->lencode != 1 || !up->msgcnt)
		    return;
		/* else, OTM; drop out of switch */
	}

	pp->leap = LEAP_NOWARNING;
	pp->day = day;
	pp->hour = hour;
	pp->minute = minute;
	pp->second = second;

	/*
	 * Colossal hack here. We process each sample in a trimmed-mean
	 * filter and determine the reference clock offset and
	 * dispersion. The fudge time1 value is added to each sample as
	 * received.
	 */
	if (!refclock_process(pp)) {
#ifdef DEBUG
		if (debug)
		    printf("usno: time rejected\n");
#endif
		refclock_report(peer, CEVNT_BADTIME);
		return;
	} else if (up->msgcnt < MSGCNT)
	    return;

	/*
	 * We have a filtered sample offset ready for peer processing.
	 * We use lastrec as both the reference time and receive time in
	 * order to avoid being cute, like setting the reference time
	 * later than the receive time, which may cause a paranoid
	 * protocol module to chuck out the data. Finaly, we unhook the
	 * timeout, arm for the next call, fold the tent and go home.
	 */
	record_clock_stats(&peer->srcadr, pp->a_lastcode);
	refclock_receive(peer);
	pp->sloppyclockflag &= ~CLK_FLAG1;
	up->pollcnt = 0;
	up->state = 0;
	usno_disc(peer);
}
#endif /* 0 */


/*
 * usno_poll - called by the transmit routine
 */
static void
usno_poll(
	int unit,
	struct peer *peer
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;

	/*
	 * If the driver is running, we set the enable flag (fudge
	 * flag1), which causes the driver timeout routine to initiate a
	 * call. If not, the enable flag can be set using
	 * ntpdc. If this is the sustem peer, then follow the system
	 * poll interval.
	 */
	pp = peer->procptr;
	up = (struct usnounit *)pp->unitptr;
	if (up->run) {
		pp->sloppyclockflag |= CLK_FLAG1;
		if (peer == sys_peer)
		    peer->hpoll = sys_poll;
		else
		    peer->hpoll = peer->minpoll;
	}
}


#if 0
/*
 * usno_timeout - called by the timer interrupt
 */
static void
usno_timeout(
	struct peer *peer
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;
	int fd;
	char device[20];
	char lockfile[128], pidbuf[8];
	int dtr = TIOCM_DTR;

	/*
	 * If a timeout occurs in other than state 0, the call has
	 * failed. If in state 0, we just see if there is other work to
	 * do.
	 */
	pp = peer->procptr;
	up = (struct usnounit *)pp->unitptr;
	if (up->state) {
		if (up->state != 1) {
			usno_disc(peer);
			return;
		}
		/*
		 * Call, and start the answer timeout. We think it
		 * strange if the OK status has not been received from
		 * the modem, but plow ahead anyway.
		 *
		 * This code is *here* because we had to stick in a brief
		 * delay to let the modem settle down after raising DTR,
		 * and for the OK to be received.  State machines are
		 * contorted.
		 */
		if (strcmp(pp->a_lastcode, "OK") != 0)
		    NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
			    msyslog(LOG_NOTICE, "clock %s USNO no modem status",
				    ntoa(&peer->srcadr));
		(void)usno_write(peer, PHONE);
		NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
			msyslog(LOG_NOTICE, "clock %s USNO calling %s\n",
				ntoa(&peer->srcadr), PHONE);
		up->state = 2;
		up->pollcnt++;
		pp->polls++;
		peer->nextdate = current_time + ANSWER;
		return;
	}
	switch (peer->ttlmax) {

		/*
		 * In manual mode the calling program is activated
		 * by the ntpdc program using the enable flag (fudge
		 * flag1), either manually or by a cron job.
		 */
	    case MODE_MANUAL:
		up->run = 0;
		break;

		/*
		 * In automatic mode the calling program runs
		 * continuously at intervals determined by the sys_poll
		 * variable.
		 */
	    case MODE_AUTO:
		if (!up->run)
		    pp->sloppyclockflag |= CLK_FLAG1;
		up->run = 1;
		break;

		/*
		 * In backup mode the calling program is disabled,
		 * unless no system peer has been selected for MAXOUTAGE
		 * (3600 s). Once enabled, it runs until some other NTP
		 * peer shows up.
		 */
	    case MODE_BACKUP:
		if (!up->run && sys_peer == 0) {
			if (current_time - last_time > MAXOUTAGE) {
				up->run = 1;
				peer->hpoll = peer->minpoll;
				NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
					msyslog(LOG_NOTICE,
						"clock %s USNO backup started ",
						ntoa(&peer->srcadr));
			}
		} else if (up->run && sys_peer->sstclktype != CTL_SST_TS_TELEPHONE) {
			peer->hpoll = peer->minpoll;
			up->run = 0;
			NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
				msyslog(LOG_NOTICE,
					"clock %s USNO backup stopped",
					ntoa(&peer->srcadr));
		}
		break;

	    default:
		msyslog(LOG_ERR,
			"clock %s USNO invalid mode", ntoa(&peer->srcadr));
		
	}

	/*
	 * The fudge flag1 is used as an enable/disable; if set either
	 * by the code or via ntpdc, the calling program is
	 * started; if reset, the phones stop ringing.
	 */
	if (!(pp->sloppyclockflag & CLK_FLAG1)) {
		up->pollcnt = 0;
		peer->nextdate = current_time + IDLE;
		return;
	}

	/*
	 * Lock the port.
	 */
	(void)sprintf(lockfile, LOCKFILE, up->unit);
	fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0644);
	if (fd < 0) {
		msyslog(LOG_ERR, "clock %s USNO port busy",
			ntoa(&peer->srcadr));
		return;
	}
	sprintf(pidbuf, "%d\n", (int) getpid());
	write(fd, pidbuf, strlen(pidbuf));
	close(fd);

	/*
	 * Open serial port. Use ACTS line discipline, if available. It
	 * pumps a timestamp into the data stream at every on-time
	 * character '*' found. Note: the port must have modem control
	 * or deep pockets for the phone bill. HP-UX 9.03 users should
	 * have very deep pockets.
	 */
	(void)sprintf(device, DEVICE, up->unit);
	if (!(fd = refclock_open(device, SPEED232, LDISC_ACTS))) {
		unlink(lockfile);
		return;
	}
	if (ioctl(fd, TIOCMBIC, (char *)&dtr) < 0)
	    msyslog(LOG_WARNING, "usno_timeout: clock %s: couldn't clear DTR: %m",
		    ntoa(&peer->srcadr));

	pp->io.clock_recv = usno_receive;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;
	pp->io.fd = fd;
	if (!io_addclock(&pp->io)) {
		(void) close(fd);
		unlink(lockfile);
		free(up);
		return;
	}

	/*
	 * Initialize modem and kill DTR. We skedaddle if this comes
	 * bum.
	 */
	if (!usno_write(peer, MODEM_SETUP)) {
		msyslog(LOG_ERR, "clock %s USNO couldn't write",
			ntoa(&peer->srcadr));
		io_closeclock(&pp->io);
		unlink(lockfile);
		free(up);
		return;
	}

	/*
	 * Initiate a call to the Observatory. If we wind up here in
	 * other than state 0, a successful call could not be completed
	 * within minpoll seconds.
	 */
	if (up->pollcnt) {
		refclock_report(peer, CEVNT_TIMEOUT);
		NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
			msyslog(LOG_NOTICE,
				"clock %s USNO calling program terminated",
				ntoa(&peer->srcadr));
		pp->sloppyclockflag &= ~CLK_FLAG1;
		up->pollcnt = 0;
#ifdef DEBUG
		if (debug)
		    printf("usno: calling program terminated\n");
#endif
		usno_disc(peer);
		return;
	}

	/*
	 * Raise DTR, and let the modem settle.  Then we'll dial.
	 */
	if (ioctl(pp->io.fd, TIOCMBIS, (char *)&dtr) < -1)
	    msyslog(LOG_INFO, "usno_timeout: clock %s: couldn't set DTR: %m",
		    ntoa(&peer->srcadr));
	up->state = 1;
	peer->nextdate = current_time + WAIT;
}
#endif /* 0 */


/*
 * usno_disc - disconnect the call and wait for the ruckus to cool
 */
static void
usno_disc(
	struct peer *peer
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;
	int dtr = TIOCM_DTR;
	char lockfile[128];

	/*
	 * We should never get here other than in state 0, unless a call
	 * has timed out. We drop DTR, which will reliably get the modem
	 * off the air, even while the modem is hammering away full tilt.
	 */
	pp = peer->procptr;
	up = (struct usnounit *)pp->unitptr;

	if (ioctl(pp->io.fd, TIOCMBIC, (char *)&dtr) < 0)
	    msyslog(LOG_INFO, "usno_disc: clock %s: couldn't clear DTR: %m",
		    ntoa(&peer->srcadr));

	if (up->state > 0) {
		up->state = 0;
		msyslog(LOG_NOTICE, "clock %s USNO call failed %d",
			ntoa(&peer->srcadr), up->state);
#ifdef DEBUG
		if (debug)
		    printf("usno: call failed %d\n", up->state);
#endif
	}

	io_closeclock(&pp->io);
	sprintf(lockfile, LOCKFILE, up->unit);
	unlink(lockfile);

	peer->nextdate = current_time + WAIT;
}


#if 0
/*
 * usno_write - write a message to the serial port
 */
static int
usno_write(
	struct peer *peer,
	const char *str
	)
{
	register struct usnounit *up;
	struct refclockproc *pp;
	int len;
	int code;
	char cr = '\r';

	/*
	 * Not much to do here, other than send the message, handle
	 * debug and report faults.
	 */
	pp = peer->procptr;
	up = (struct usnounit *)pp->unitptr;
	len = strlen(str);
#ifdef DEBUG
	if (debug)
	    printf("usno: state %d send %d %s\n", up->state, len,
		   str);
#endif
	code = write(pp->io.fd, str, (unsigned)len) == len;
	code |= write(pp->io.fd, &cr, 1) == 1;
	if (!code)
	    refclock_report(peer, CEVNT_FAULT);
	return (code);
}
#endif /* 0 */

#else
int refclock_usno_bs;
#endif /* REFCLOCK */