/*-
 * Copyright (c) 1985, 1993 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

char copyright[] =
 "@(#) Copyright (c) 1985, 1993 The Regents of the University of California.\n"
 "All rights reserved.\n";

/*
 * From: @(#)timed.c	5.1 (Berkeley) 5/11/93
 */
char timed_rcsid[] = 
  "$Id: timed.c,v 1.11 1999/12/12 18:05:07 dholland Exp $";

#ifdef sgi
#ident "$Revision: 1.11 $"
#endif /* sgi */

#define TSPTYPES
#include "globals.h"
#include <net/if.h>

#if 0
	#include <sys/file.h>
	#include <sys/types.h>
#endif

#include <sys/ioctl.h>
#include <setjmp.h>
#include <math.h>
#include <sys/times.h>
#ifdef sgi
#include <unistd.h>
#include <sys/syssgi.h>
#include <sys/schedctl.h>
#endif /* sgi */

#include "../../version.h"

#ifdef __linux__
/* from libbsd.a */
int daemon(int, int);
#endif


int trace = 0;
int sock, sock_raw = -1;
int status = 0;
u_short sequence;			/* sequence number */
long delay1;
long delay2;

int nslavenets;				/* nets were I could be a slave */
int nmasternets;			/* nets were I could be a master */
int nignorednets;			/* ignored nets */
int nnets;				/* nets I am connected to */

FILE *fd;				/* trace file FD */

sigjmp_buf jmpenv;

struct netinfo *nettab = 0;
struct netinfo *slavenet;
int Mflag;
int justquit = 0;
int debug;

static struct nets {
	char	*name;
	unsigned long net;
	struct nets *next;
} *nets = 0;

struct hosttbl hosttbl[NHOSTS+1];	/* known hosts */

static struct goodhost {		/* hosts that we trust */
	char	name[MAXHOSTNAMELEN+1];
	struct goodhost *next;
	char	perm;
} *goodhosts;

static char *goodgroup;			/* net group of trusted hosts */
static void checkignorednets(void);
static void pickslavenet(struct netinfo *);
static void add_good_host(char*,char);

#ifdef sgi
char *timetrim_fn;
char *timetrim_wpat = "long timetrim = %ld;\ndouble tot_adj = %.0f;\ndouble tot_ticks = %.0f;\n/* timed version 2 */\n";
char *timetrim_rpat = "long timetrim = %ld;\ndouble tot_adj = %lf;\ndouble tot_ticks = %lf;";
long timetrim;
double tot_adj, hr_adj;			/* totals in nsec */
double tot_ticks, hr_ticks;

int bufspace = 60*1024;
#endif


/*
 * The timedaemons synchronize the clocks of hosts in a local area network.
 * One daemon runs as master, all the others as slaves. The master
 * performs the task of computing clock differences and sends correction
 * values to the slaves.
 * Slaves start an election to choose a new master when the latter disappears
 * because of a machine crash, network partition, or when killed.
 * A resolution protocol is used to kill all but one of the masters
 * that happen to exist in segments of a partitioned network when the
 * network partition is fixed.
 *
 * Authors: Riccardo Gusella & Stefano Zatti
 *
 * overhauled at Silicon Graphics
 */
int
main(int argc, char **argv)
{
	int on;
	int ret;
	int nflag, iflag;
	struct timeval ntime;
	struct servent *srvp;
	char buf[BUFSIZ], *cp, *cplim;
	struct ifconf ifc;
	struct ifreq ifreq, ifreqf, *ifr;
	register struct netinfo *ntp;
	struct netinfo *ntip;
	struct netinfo *savefromnet;
	struct netent *nentp;
	struct nets *nt;
	struct sockaddr_in server;
	u_short port;
	char c;
#ifdef sgi
	FILE *timetrim_st;
#endif

#define	IN_MSG "timed: -i and -n make no sense together\n"
#ifdef sgi
	struct tms tms;
#define USAGE "timed: [-dtM] [-i net|-n net] [-F host1 host2 ...] [-G netgp] [-P trimfile]\n"
#else
#ifdef HAVENIS
#define USAGE "timed: [-dtM] [-i net|-n net] [-F host1 host2 ...] [-G netgp]\n"
#else
#define USAGE "timed: [-dtM] [-i net|-n net] [-F host1 host2 ...]\n"
#endif /* HAVENIS */
#endif /* sgi */

	ntip = NULL;

	on = 1;
	nflag = OFF;
	iflag = OFF;

#ifdef sgi
	if (0 > syssgi(SGI_GETTIMETRIM, &timetrim)) {
		perror("timed: syssgi(GETTIMETRIM)");
		timetrim = 0;
	}
	tot_ticks = hr_ticks = times(&tms);
#endif /* sgi */

	opterr = 0;
	while ((c = getopt(argc, argv, "Mtdn:i:F:G:P:")) != EOF) {
		switch (c) {
		case 'M':
			Mflag = 1;
			break;

		case 't':
			trace = 1;
			break;

		case 'n':
			if (iflag) {
				fprintf(stderr, IN_MSG);
				exit(1);
			} else {
				nflag = ON;
				addnetname(optarg);
			}
			break;

		case 'i':
			if (nflag) {
				fprintf(stderr, IN_MSG);
				exit(1);
			} else {
				iflag = ON;
				addnetname(optarg);
			}
			break;

		case 'F':
			add_good_host(optarg,1);
			while (optind < argc && argv[optind][0] != '-')
				add_good_host(argv[optind++], 1);
			break;

		case 'd':
			debug = 1;
			break;
		case 'G':
			if (goodgroup != 0) {
				fprintf(stderr,"timed: only one net group\n");
				exit(1);
			}
			goodgroup = optarg;
			break;
#ifdef sgi
		case 'P':
			timetrim_fn = optarg;
			break;
#endif /* sgi */

		default:
			fprintf(stderr, USAGE);
			exit(1);
			break;
		}
	}
	if (optind < argc) {
		fprintf(stderr, USAGE);
		exit(1);
	}

#ifdef sgi
	if (timetrim_fn == 0) {
		;
	} else if (0 == (timetrim_st = fopen(timetrim_fn, "r+"))) {
		if (errno != ENOENT) {
			(void)fprintf(stderr,"timed: ");
			perror(timetrim_fn);
			timetrim_fn = 0;
		}
	} else {
		int i;
		long trim;
		double adj, ticks;

		i = fscanf(timetrim_st, timetrim_rpat,
			   &trim, &adj, &ticks);
		if (i < 1
		    || trim > MAX_TRIM
		    || trim < -MAX_TRIM
		    || i == 2
		    || (i == 3
			&& trim != rint(adj*CLK_TCK/ticks))) {
			if (trace && i != EOF)
				(void)fprintf(stderr,
		    "timed: unrecognized contents in %s\n",
					      timetrim_fn);
		} else {
			if (0 > syssgi(SGI_SETTIMETRIM,
				       trim)) {
			 perror("timed: syssgi(SETTIMETRIM)");
			} else {
				timetrim = trim;
			}
			if (i == 3)
				tot_ticks -= ticks;
		}
		(void)fclose(timetrim_st);
	}
#endif /* sgi */

	/* If we care about which machine is the master, then we must
	 *	be willing to be a master
	 */
	if (0 != goodgroup || 0 != goodhosts)
		Mflag = 1;

	if (gethostname(hostname, sizeof(hostname) - 1) < 0) {
		perror("gethostname");
		exit(1);
	}
	self.l_bak = &self;
	self.l_fwd = &self;
	self.h_bak = &self;
	self.h_fwd = &self;
	self.head = 1;
	self.good = 1;

	if (goodhosts != 0)		/* trust ourself */
		add_good_host(hostname,1);

	srvp = getservbyname("timed", "udp");
	if (srvp == 0) {
		fprintf(stderr, "unknown service 'timed/udp'\n");
		exit(1);
	}
	port = srvp->s_port;
	memset(&server, 0, sizeof(server));
	server.sin_port = srvp->s_port;
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		perror("socket");
		exit(1);
	}
	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&on,
							sizeof(on)) < 0) {
		perror("setsockopt");
		exit(1);
	}
	if (bind(sock, (struct sockaddr*)&server, sizeof(server))) {
		if (errno == EADDRINUSE)
			fprintf(stderr,"timed: time daemon already running\n");
		else
			perror("bind");
		exit(1);
	}
#ifdef sgi
	/*
	 * handle many slaves with our buffer
	 */
	if (0 > setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&bufspace,
			 sizeof(bufspace))) {
		perror("setsockopt");
		exit(1);
	}
#endif /* sgi */

	/* choose a unique seed for random number generation */
	(void)gettimeofday(&ntime, 0);
	srandom(ntime.tv_sec + ntime.tv_usec);

	sequence = random();     /* initial seq number */

#ifndef sgi
	/* rounds kernel variable time to multiple of 5 ms. */
	ntime.tv_sec = 0;
	ntime.tv_usec = -((ntime.tv_usec/1000) % 5) * 1000;
	(void)adjtime(&ntime, (struct timeval *)0);
#endif /* sgi */

	for (nt = nets; nt; nt = nt->next) {
		nentp = getnetbyname(nt->name);
		if (nentp == 0) {
			nt->net = inet_network(nt->name);
			if (nt->net != INADDR_NONE)
				nentp = getnetbyaddr(nt->net, AF_INET);
		}
		if (nentp != 0) {
			nt->net = nentp->n_net;
		} else if (nt->net == INADDR_NONE) {
			fprintf(stderr, "timed: unknown net %s\n", nt->name);
			exit(1);
		} else if (nt->net == INADDR_ANY) {
			fprintf(stderr, "timed: bad net %s\n", nt->name);
			exit(1);
		} else {
			fprintf(stderr,
				"timed: warning: %s unknown in /etc/networks\n",
				nt->name);
		}

		/* WTF? */
		/*
		if (0 == (nt->net & 0xff000000))
		    nt->net <<= 8;
		if (0 == (nt->net & 0xff000000))
		    nt->net <<= 8;
		if (0 == (nt->net & 0xff000000))
		    nt->net <<= 8;
		*/
		nt->net = htonl(nt->net);

	}
	ifc.ifc_len = sizeof(buf);
	ifc.ifc_buf = buf;
	if (ioctl(sock, SIOCGIFCONF, (char *)&ifc) < 0) {
		perror("timed: get interface configuration");
		exit(1);
	}
	ntp = NULL;
#ifdef sgi
#define size(p)	(sizeof(*ifr) - sizeof(ifr->ifr_name))  /* XXX hack. kludge */
#else
#define size(p)	max((p).sa_len, sizeof(p))
#endif
	cplim = buf + ifc.ifc_len; /*skip over if's with big ifr_addr's */

/*	for (cp = buf; cp < cplim;
		cp += sizeof (ifr->ifr_name) + sizeof(ifr->ifr_addr)) { */

	for (cp = buf; cp < cplim; cp += sizeof(struct ifreq)) {
		ifr = (struct ifreq *)cp;
		if (ifr->ifr_addr.sa_family != AF_INET)
			continue;
		if (!ntp)
			ntp = (struct netinfo*)malloc(sizeof(struct netinfo));
		bzero(ntp,sizeof(*ntp));
		ntp->my_addr=((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr;
		ntp->status = NOMASTER;
		ifreq = *ifr;
		ifreqf = *ifr;

		if (ioctl(sock, SIOCGIFFLAGS, (char *)&ifreqf) < 0) {
			perror("get interface flags");
			continue;
		}
		if ((ifreqf.ifr_flags & IFF_UP) == 0)
			continue;
		if ((ifreqf.ifr_flags & IFF_BROADCAST) == 0 &&
		    (ifreqf.ifr_flags & IFF_POINTOPOINT) == 0) {
			continue;
		}


		if (ioctl(sock, SIOCGIFNETMASK, (char *)&ifreq) < 0) {
			perror("get netmask");
			continue;
		}
		ntp->mask = ((struct sockaddr_in *)
			&ifreq.ifr_addr)->sin_addr.s_addr;

		if (ifreqf.ifr_flags & IFF_BROADCAST) {
			if (ioctl(sock, SIOCGIFBRDADDR, (char *)&ifreq) < 0) {
				perror("get broadaddr");
				continue;
			}
			ntp->dest_addr = *(struct sockaddr_in *)&ifreq.ifr_broadaddr;
			/* What if the broadcast address is all ones?
			 * So we cannot just mask ntp->dest_addr.  */
			ntp->net = ntp->my_addr;
			ntp->net.s_addr &= ntp->mask;
		} else {
			if (ioctl(sock, SIOCGIFDSTADDR,
						(char *)&ifreq) < 0) {
				perror("get destaddr");
				continue;
			}
			ntp->dest_addr = *(struct sockaddr_in *)&ifreq.ifr_dstaddr;
			ntp->net = ntp->dest_addr.sin_addr;
		}

		ntp->dest_addr.sin_port = port;

		for (nt = nets; nt; nt = nt->next) {
			if (ntp->net.s_addr == nt->net)
				break;
		}
		if ((nflag && !nt) || (iflag && nt))
			continue;

		ntp->next = NULL;
		if (nettab == NULL) {
			nettab = ntp;
		} else {
			ntip->next = ntp;
		}
		ntip = ntp;
		ntp = NULL;
	}
	if (ntp)
		(void) free((char *)ntp);
	if (nettab == NULL) {
		fprintf(stderr, "timed: no network usable\n");
		exit(1);
	}


#ifdef sgi
	(void)schedctl(RENICE,0,10);	   /* run fast to get good time */

	/* ticks to delay before responding to a broadcast */
	delay1 = casual(0, CLK_TCK/10);
#else

	/* microseconds to delay before responding to a broadcast */
	delay1 = casual(1, 100*1000);
#endif /* sgi */

	/* election timer delay in secs. */
	delay2 = casual(MINTOUT, MAXTOUT);


#ifdef sgi
	(void)_daemonize(debug ? _DF_NOFORK|_DF_NOCHDIR : 0, sock, -1, -1);
#else
	if (!debug)
		daemon(debug, 0);
#endif /* sgi */

	if (trace)
		traceon();
	openlog("timed", LOG_CONS|LOG_PID, LOG_DAEMON);

	/*
	 * keep returning here
	 */
	ret = sigsetjmp(jmpenv, 1);
	savefromnet = fromnet;
	setstatus();

	if (Mflag) {
		switch (ret) {

		case 0:
			checkignorednets();
			pickslavenet(0);
			break;
		case 1:
			/* Just lost our master */
			if (slavenet != 0)
				slavenet->status = election(slavenet);
			if (!slavenet || slavenet->status == MASTER) {
				checkignorednets();
				pickslavenet(0);
			} else {
				makeslave(slavenet);	/* prune extras */
			}
			break;

		case 2:
			/* Just been told to quit */
			justquit = 1;
			pickslavenet(savefromnet);
			break;
		}

		setstatus();
		if (!(status & MASTER) && sock_raw != -1) {
			/* sock_raw is not being used now */
			(void)close(sock_raw);
			sock_raw = -1;
		}

		if (status == MASTER)
			master();
		else
			slave();

	} else {
		if (sock_raw != -1) {
			(void)close(sock_raw);
			sock_raw = -1;
		}

		if (ret) {
			/* we just lost our master or were told to quit */
			justquit = 1;
		}
		for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
			if (ntp->status == MASTER)
				rmnetmachs(ntp);
				ntp->status = NOMASTER;
		}
		checkignorednets();
		pickslavenet(0);
		setstatus();

		slave();
	}
	/* NOTREACHED */
	return 0;
}


/* suppress an upstart, untrustworthy, self-appointed master
 */
void
suppress(struct sockaddr_in *addr,
         char *name,
	 struct netinfo *net)
{
	struct sockaddr_in tgt;
	char tname[MAXHOSTNAMELEN];
	struct tsp msg;
	static struct timeval wait;

	if (trace)
		fprintf(fd, "suppress: %s\n", name);
	tgt = *addr;
	(void)strcpy(tname, name);

	while (0 != readmsg(TSP_ANY, ANYADDR, &wait, net)) {
		if (trace)
			fprintf(fd, "suppress:\tdiscarded packet from %s\n",
				    name);
	}

	syslog(LOG_NOTICE, "suppressing false master %s", tname);
	msg.tsp_type = TSP_QUIT;
	(void)strcpy(msg.tsp_name, hostname);
	(void)acksend(&msg, &tgt, tname, TSP_ACK, 0, 1);
}

void
lookformaster(struct netinfo *ntp)
{
	struct tsp resp, conflict, *answer;
	struct timeval ntime;
	char mastername[MAXHOSTNAMELEN];
	struct sockaddr_in masteraddr;

	get_goodgroup(0);
	ntp->status = SLAVE;

	/* look for master */
	resp.tsp_type = TSP_MASTERREQ;
	(void)strcpy(resp.tsp_name, hostname);
	answer = acksend(&resp, &ntp->dest_addr, ANYADDR,
			 TSP_MASTERACK, ntp, 0);
	if (answer != 0 && !good_host_name(answer->tsp_name)) {
		suppress(&from, answer->tsp_name, ntp);
		ntp->status = NOMASTER;
		answer = 0;
	}
	if (answer == 0) {
		/*
		 * Various conditions can cause conflict: races between
		 * two just started timedaemons when no master is
		 * present, or timedaemons started during an election.
		 * A conservative approach is taken.  Give up and became a
		 * slave, postponing election of a master until first
		 * timer expires.
		 */
		ntime.tv_sec = ntime.tv_usec = 0;
		answer = readmsg(TSP_MASTERREQ, ANYADDR, &ntime, ntp);
		if (answer != 0) {
			if (!good_host_name(answer->tsp_name)) {
				suppress(&from, answer->tsp_name, ntp);
				ntp->status = NOMASTER;
			}
			return;
		}

		ntime.tv_sec = ntime.tv_usec = 0;
		answer = readmsg(TSP_MASTERUP, ANYADDR, &ntime, ntp);
		if (answer != 0) {
			if (!good_host_name(answer->tsp_name)) {
				suppress(&from, answer->tsp_name, ntp);
				ntp->status = NOMASTER;
			}
			return;
		}

		ntime.tv_sec = ntime.tv_usec = 0;
		answer = readmsg(TSP_ELECTION, ANYADDR, &ntime, ntp);
		if (answer != 0) {
			if (!good_host_name(answer->tsp_name)) {
				suppress(&from, answer->tsp_name, ntp);
				ntp->status = NOMASTER;
			}
			return;
		}

		if (Mflag)
			ntp->status = MASTER;
		else
			ntp->status = NOMASTER;
		return;
	}

	ntp->status = SLAVE;
	(void)strcpy(mastername, answer->tsp_name);
	masteraddr = from;

	/*
	 * If network has been partitioned, there might be other
	 * masters; tell the one we have just acknowledged that
	 * it has to gain control over the others.
	 */
	ntime.tv_sec = 0;
	ntime.tv_usec = 300000;
	answer = readmsg(TSP_MASTERACK, ANYADDR, &ntime, ntp);
	/*
	 * checking also not to send CONFLICT to ack'ed master
	 * due to duplicated MASTERACKs
	 */
	if (answer != NULL &&
	    strcmp(answer->tsp_name, mastername) != 0) {
		conflict.tsp_type = TSP_CONFLICT;
		(void)strcpy(conflict.tsp_name, hostname);
		if (!acksend(&conflict, &masteraddr, mastername,
			     TSP_ACK, 0, 0)) {
			syslog(LOG_ERR,
			       "error on sending TSP_CONFLICT");
		}
	}
}

/*
 * based on the current network configuration, set the status, and count
 * networks;
 */
void setstatus(void)
{
	struct netinfo *ntp;

	status = 0;
	nmasternets = nslavenets = nnets = nignorednets = 0;
	if (trace)
		fprintf(fd, "Net status:\n");
	for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
		switch ((int)ntp->status) {
		case MASTER:
			nmasternets++;
			break;
		case SLAVE:
			nslavenets++;
			break;
		case NOMASTER:
		case IGNORE:
			nignorednets++;
			break;
		}
		if (trace) {
			fprintf(fd, "\t%-16s", inet_ntoa(ntp->net));
			switch ((int)ntp->status) {
			case NOMASTER:
				fprintf(fd, "NOMASTER\n");
				break;
			case MASTER:
				fprintf(fd, "MASTER\n");
				break;
			case SLAVE:
				fprintf(fd, "SLAVE\n");
				break;
			case IGNORE:
				fprintf(fd, "IGNORE\n");
				break;
			default:
				fprintf(fd, "invalid state %d\n",
					(int)ntp->status);
				break;
			}
		}
		nnets++;
		status |= ntp->status;
	}
	status &= ~IGNORE;
	if (trace)
		fprintf(fd,
			"\tnets=%d masters=%d slaves=%d ignored=%d delay2=%ld\n",
			nnets, nmasternets, nslavenets, nignorednets, delay2);
}

void
makeslave(struct netinfo *net)
{
	register struct netinfo *ntp;

	for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
		if (ntp->status == SLAVE && ntp != net)
			ntp->status = IGNORE;
	}
	slavenet = net;
}

/*
 * Try to become master over ignored nets..
 */
static void
checkignorednets(void)
{
	register struct netinfo *ntp;

	for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
		if (!Mflag && ntp->status == SLAVE)
			break;

		if (ntp->status == IGNORE || ntp->status == NOMASTER) {
			lookformaster(ntp);
			if (!Mflag && ntp->status == SLAVE)
				break;
		}
	}
}

/*
 * choose a good network on which to be a slave
 *	The ignored networks must have already been checked.
 *	Take a hint about for a good network.
 */
static void
pickslavenet(struct netinfo *ntp)
{
	if (slavenet != 0 && slavenet->status == SLAVE) {
		makeslave(slavenet);		/* prune extras */
		return;
	}

	if (ntp == 0 || ntp->status != SLAVE) {
		for (ntp = nettab; ntp != 0; ntp = ntp->next) {
			if (ntp->status == SLAVE)
				break;
		}
	}
	makeslave(ntp);
}

/*
 * returns a random number in the range [inf, sup]
 */
long
casual(long inf, long sup)
{
	double value;

	value = ((double)(random() & 0x7fffffff)) / (0x7fffffff*1.0);
	return(inf + (sup - inf)*value);
}

char *date(void)
{
#ifdef sgi
	struct	timeval tv;
	static char tm[32];

	(void)gettimeofday(&tv, (struct timezone *)0);
	(void)cftime(tm, "%D %T", &tv.tv_sec);
	return (tm);
#else
/*	struct	timeval tv; */
	time_t tm;
	time(&tm);
	return (ctime(&tm));
#endif /* sgi */
}

void
addnetname(char *name)
{
	register struct nets **netlist = &nets;

	while (*netlist)
		netlist = &((*netlist)->next);
	*netlist = (struct nets *)malloc(sizeof **netlist);
	if (*netlist == 0) {
		fprintf(stderr,"malloc failed\n");
		exit(1);
	}
	bzero((char *)*netlist, sizeof(**netlist));
	(*netlist)->name = name;
}

/* note a host as trustworthy */
static void
add_good_host(char* name,
	      char perm)		/* 1=not part of the netgroup */
{
	register struct goodhost *ghp;
	register struct hostent *hentp;

	ghp = (struct goodhost*)malloc(sizeof(*ghp));
	if (!ghp) {
		syslog(LOG_ERR, "malloc failed");
		exit(1);
	}

	bzero((char*)ghp, sizeof(*ghp));
	(void)strncpy(&ghp->name[0], name, sizeof(ghp->name));
	ghp->next = goodhosts;
	ghp->perm = perm;
	goodhosts = ghp;

	hentp = gethostbyname(name);
	if (0 == hentp && perm)
		(void)fprintf(stderr, "unknown host %s\n", name);
}


/* update our image of the net-group of trustworthy hosts
 * XXX: CLK_TCK may not be a constant.
 */
void
get_goodgroup(int force)
{
# define NG_DELAY (30*60*CLK_TCK)	/* 30 minutes */
# ifdef CONSTANT_CLK_TCK
	static unsigned long last_update = -NG_DELAY;
# else
	static int last_update_init = 0;
	static unsigned long last_update = 0;
# endif
	unsigned long new_update;
/*	struct hosttbl *htp; */
	struct goodhost *ghp, **ghpp;
/*	char *mach, *usr, *dom; */
	struct tms tm;

# ifndef CONSTANT_CLK_TCK
	if (!last_update_init) {
		last_update = -NG_DELAY;
		last_update_init = 1;
	}
# endif

	/* if no netgroup, then we are finished */
	if (goodgroup == 0 || !Mflag)
		return;

	/* Do not chatter with the netgroup master too often.
	 */
	new_update = times(&tm);
	if (new_update < last_update + NG_DELAY
	    && !force)
		return;
	last_update = new_update;

	/* forget the old temporary entries */
	ghpp = &goodhosts;
	while (0 != (ghp = *ghpp)) {
		if (!ghp->perm) {
			*ghpp = ghp->next;
			free((char*)ghp);
		} else {
			ghpp = &ghp->next;
		}
	}

#ifdef HAVENIS
	/* quit now if we are not one of the trusted masters
	 */
	if (!innetgr(goodgroup, &hostname[0], 0,0)) {
		if (trace)
			(void)fprintf(fd, "get_goodgroup: %s not in %s\n",
				      &hostname[0], goodgroup);
		return;
	}
	if (trace)
		(void)fprintf(fd, "get_goodgroup: %s in %s\n",
				  &hostname[0], goodgroup);

	/* mark the entire netgroup as trusted */
	(void)setnetgrent(goodgroup);
	while (getnetgrent(&mach,&usr,&dom)) {
		if (0 != mach)
			add_good_host(mach,0);
	}
	(void)endnetgrent();

	/* update list of slaves */
	for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
		htp->good = good_host_name(&htp->name[0]);
	}
#endif /* HAVENIS */
}

/* see if a machine is trustworthy
 * 1=trust hp to change our date 
 */
 
int good_host_name(char *name)
{
	register struct goodhost *ghp = goodhosts;
	register char c;

	if (!ghp || !Mflag)		/* trust everyone if no one named */
		return 1;

	c = *name;
	do {
		if (c == ghp->name[0]
		    && !strcasecmp(name, ghp->name))
			return 1;	/* found him, so say so */
	} while (0 != (ghp = ghp->next));

	if (!strcasecmp(name,hostname))	/* trust ourself */
		return 1;

	return 0;			/* did not find him */
}
