text: /*-
   * Copyright (c) 1985, 1993
   *	The Regents of the University of California.  All rights reserved.
   *
   * %sccs.include.redist.c%
   */
  
  #ifndef lint
  static char sccsid[] = "@(#)slave.c	8.1 (Berkeley) 06/06/93";
  #endif /* not lint */
  
  #ifdef sgi
  #ident "$Revision: 1.20 $"
  #endif
  
  #include "globals.h"
  #include <setjmp.h>
  #include "pathnames.h"
  
  extern jmp_buf jmpenv;
  extern int Mflag;
  extern int justquit;
  
  extern u_short sequence;
  
  static char master_name[MAXHOSTNAMELEN+1];
  static struct netinfo *old_slavenet;
  static int old_status;
  
  static void schgdate __P((struct tsp *, char *));
  static void setmaster __P((struct tsp *));
  static void answerdelay __P((void));
  
  #ifdef sgi
  extern void logwtmp __P((struct timeval *, struct timeval *));
  #else
  extern void logwtmp __P((char *, char *, char *));
  #endif /* sgi */
  
  int
  slave()
  {
  	int tries;
  	long electiontime, refusetime, looktime, looptime, adjtime;
  	u_short seq;
  	long fastelection;
  #define FASTTOUT 3
  	struct in_addr cadr;
  	struct timeval otime;
  	struct sockaddr_in taddr;
  	char tname[MAXHOSTNAMELEN];
  	struct tsp *msg, to;
  	struct timeval ntime, wait;
  	struct tsp *answer;
  	int timeout();
  	char olddate[32];
  	char newdate[32];
  	struct netinfo *ntp;
  	struct hosttbl *htp;
  
  
  	old_slavenet = 0;
  	seq = 0;
  	refusetime = 0;
  	adjtime = 0;
  
  	(void)gettimeofday(&ntime, 0);
  	electiontime = ntime.tv_sec + delay2;
  	fastelection = ntime.tv_sec + FASTTOUT;
  	if (justquit)
  		looktime = electiontime;
  	else
  		looktime = fastelection;
  	looptime = fastelection;
  
  	if (slavenet)
  		xmit(TSP_SLAVEUP, 0, &slavenet->dest_addr);
  	if (status & MASTER) {
  		for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
  			if (ntp->status == MASTER)
  				masterup(ntp);
  		}
  	}
  
  loop:
  	get_goodgroup(0);
  	(void)gettimeofday(&ntime, (struct timezone *)0);
  	if (ntime.tv_sec > electiontime) {
  		if (trace)
  			fprintf(fd, "election timer expired\n");
  		longjmp(jmpenv, 1);
  	}
  
  	if (ntime.tv_sec >= looktime) {
  		if (trace)
  			fprintf(fd, "Looking for nets to master\n");
  
  		if (Mflag && nignorednets > 0) {
  			for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
  				if (ntp->status == IGNORE
  				    || ntp->status == NOMASTER) {
  					lookformaster(ntp);
  					if (ntp->status == MASTER) {
  						masterup(ntp);
  					} else if (ntp->status == MASTER) {
  						ntp->status = NOMASTER;
  					}
  				}
  				if (ntp->status == MASTER
  				    && --ntp->quit_count < 0)
  					ntp->quit_count = 0;
  			}
  			makeslave(slavenet);	/* prune extras */
  			setstatus();
  		}
  		(void)gettimeofday(&ntime, 0);
  		looktime = ntime.tv_sec + delay2;
  	}
  	if (ntime.tv_sec >= looptime) {
  		if (trace)
  			fprintf(fd, "Looking for loops\n");
  		for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
  		    if (ntp->status == MASTER) {
  			to.tsp_type = TSP_LOOP;
  			to.tsp_vers = TSPVERSION;
  			to.tsp_seq = sequence++;
  			to.tsp_hopcnt = MAX_HOPCNT;
  			(void)strcpy(to.tsp_name, hostname);
  			bytenetorder(&to);
  			if (sendto(sock, (char *)&to, sizeof(struct tsp), 0,
  				   (struct sockaddr*)&ntp->dest_addr,
  				   sizeof(ntp->dest_addr)) < 0) {
  				trace_sendto_err(ntp->dest_addr.sin_addr);
  			}
  		    }
  		}
  		(void)gettimeofday(&ntime, 0);
  		looptime = ntime.tv_sec + delay2;
  	}
  
  	wait.tv_sec = min(electiontime,min(looktime,looptime)) - ntime.tv_sec;
  	if (wait.tv_sec < 0)
  		wait.tv_sec = 0;
  	wait.tv_sec += FASTTOUT;
  	wait.tv_usec = 0;
  	msg = readmsg(TSP_ANY, ANYADDR, &wait, 0);
  
  	if (msg != NULL) {
  		/*
  		 * filter stuff not for us
  		 */
  		switch (msg->tsp_type) {
  		case TSP_SETDATE:
  		case TSP_TRACEOFF:
  		case TSP_TRACEON:
  			/*
  			 * XXX check to see they are from ourself
  			 */
  			break;
  
  		case TSP_TEST:
  		case TSP_MSITE:
  			break;
  
  		case TSP_MASTERUP:
  			if (!fromnet) {
  				if (trace) {
  					fprintf(fd, "slave ignored: ");
  					print(msg, &from);
  				}
  				goto loop;
  			}
  			break;
  
  		default:
  			if (!fromnet
  			    || fromnet->status == IGNORE
  			    || fromnet->status == NOMASTER) {
  				if (trace) {
  					fprintf(fd, "slave ignored: ");
  					print(msg, &from);
  				}
  				goto loop;
  			}
  			break;
  		}
  
  
  		/*
  		 * now process the message
  		 */
  		switch (msg->tsp_type) {
  
  		case TSP_ADJTIME:
  			if (fromnet != slavenet)
  				break;
  			if (!good_host_name(msg->tsp_name)) {
  				syslog(LOG_NOTICE,
  				   "attempted time adjustment by %s",
  				       msg->tsp_name);
  				suppress(&from, msg->tsp_name, fromnet);
  				break;
  			}
  			/*
  			 * Speed up loop detection in case we have a loop.
  			 * Otherwise the clocks can race until the loop
  			 * is found.
  			 */
  			(void)gettimeofday(&otime, 0);
  			if (adjtime < otime.tv_sec)
  				looptime -= (looptime-otime.tv_sec)/2 + 1;
  
  			setmaster(msg);
  			if (seq != msg->tsp_seq) {
  				seq = msg->tsp_seq;
  				synch(tvtomsround(msg->tsp_time));
  			}
  			(void)gettimeofday(&ntime, 0);
  			electiontime = ntime.tv_sec + delay2;
  			fastelection = ntime.tv_sec + FASTTOUT;
  			adjtime = ntime.tv_sec + SAMPLEINTVL*2;
  			break;
  
  		case TSP_SETTIME:
  			if (fromnet != slavenet)
  				break;
  			if (seq == msg->tsp_seq)
  				break;
  			seq = msg->tsp_seq;
  
  			/* adjust time for residence on the queue */
  			(void)gettimeofday(&otime, 0);
  			adj_msg_time(msg,&otime);
  #ifdef sgi
  			(void)cftime(newdate, "%D %T", &msg->tsp_time.tv_sec);
  			(void)cftime(olddate, "%D %T", &otime.tv_sec);
  #else
  			/*
  			 * the following line is necessary due to syslog
  			 * calling ctime() which clobbers the static buffer
  			 */
  			(void)strcpy(olddate, date());
  			(void)strcpy(newdate, ctime(&msg->tsp_time.tv_sec));
  #endif /* sgi */
  
  			if (!good_host_name(msg->tsp_name)) {
  				syslog(LOG_NOTICE,
  			    "attempted time setting by untrusted %s to %s",
  				       msg->tsp_name, newdate);
  				suppress(&from, msg->tsp_name, fromnet);
  				break;
  			}
  
  			setmaster(msg);
  			timevalsub(&ntime, &msg->tsp_time, &otime);
  			if (ntime.tv_sec < MAXADJ && ntime.tv_sec > -MAXADJ) {
  				/*
  				 * do not change the clock if we can adjust it
  				 */
  				synch(tvtomsround(ntime));
  			} else {
  #ifdef sgi
  				if (0 > settimeofday(&msg->tsp_time, 0)) {
  					syslog(LOG_ERR,"settimeofdate(): %m");
  					break;
  				}
  				logwtmp(&otime, &msg->tsp_time);
  #else
  				logwtmp("|", "date", "");
  				(void)settimeofday(&msg->tsp_time, 0);
  				logwtmp("}", "date", "");
  #endif /* sgi */
  				syslog(LOG_NOTICE,
  				       "date changed by %s from %s",
  					msg->tsp_name, olddate);
  				if (status & MASTER)
  					spreadtime();
  			}
  			(void)gettimeofday(&ntime, 0);
  			electiontime = ntime.tv_sec + delay2;
  			fastelection = ntime.tv_sec + FASTTOUT;
  
  /* This patches a bad protocol bug.  Imagine a system with several networks,
   * where there are a pair of redundant gateways between a pair of networks,
   * each running timed.  Assume that we start with a third machine mastering
   * one of the networks, and one of the gateways mastering the other.
   * Imagine that the third machine goes away and the non-master gateway
   * decides to replace it.  If things are timed just 'right,' we will have
   * each gateway mastering one network for a little while.  If a SETTIME
   * message gets into the network at that time, perhaps from the newly
   * masterful gateway as it was taking control, the SETTIME will loop
   * forever.  Each time a gateway receives it on its slave side, it will
   * call spreadtime to forward it on its mastered network.  We are now in
   * a permanent loop, since the SETTIME msgs will keep any clock
   * in the network from advancing.  Normally, the 'LOOP' stuff will detect
   * and correct the situation.  However, with the clocks stopped, the
   * 'looptime' timer cannot expire.  While they are in this state, the
   * masters will try to saturate the network with SETTIME packets.
   */
  			looptime = ntime.tv_sec + (looptime-otime.tv_sec)/2-1;
  			break;
  
  		case TSP_MASTERUP:
  			if (slavenet && fromnet != slavenet)
  				break;
  			if (!good_host_name(msg->tsp_name)) {
  				suppress(&from, msg->tsp_name, fromnet);
  				if (electiontime > fastelection)
  					electiontime = fastelection;
  				break;
  			}
  			makeslave(fromnet);
  			setmaster(msg);
  			setstatus();
  			answerdelay();
  			xmit(TSP_SLAVEUP, 0, &from);
  			(void)gettimeofday(&ntime, 0);
  			electiontime = ntime.tv_sec + delay2;
  			fastelection = ntime.tv_sec + FASTTOUT;
  			refusetime = 0;
  			break;
  
  		case TSP_MASTERREQ:
  			if (fromnet->status != SLAVE)
  				break;
  			(void)gettimeofday(&ntime, 0);
  			electiontime = ntime.tv_sec + delay2;
  			break;
  
  		case TSP_SETDATE:
  #ifdef sgi
  			(void)cftime(newdate, "%D %T", &msg->tsp_time.tv_sec);
  #else
  			(void)strcpy(newdate, ctime(&msg->tsp_time.tv_sec));
  #endif /* sgi */
  			schgdate(msg, newdate);
  			break;
  
  		case TSP_SETDATEREQ:
  			if (fromnet->status != MASTER)
  				break;
  #ifdef sgi
  			(void)cftime(newdate, "%D %T", &msg->tsp_time.tv_sec);
  #else
  			(void)strcpy(newdate, ctime(&msg->tsp_time.tv_sec));
  #endif /* sgi */
  			htp = findhost(msg->tsp_name);
  			if (0 == htp) {
  				syslog(LOG_WARNING,
  				       "DATEREQ from uncontrolled machine");
  				break;
  			}
  			if (!htp->good) {
  				syslog(LOG_WARNING,
  				"attempted date change by untrusted %s to %s",
  				       htp->name, newdate);
  				spreadtime();
  				break;
  			}
  			schgdate(msg, newdate);
  			break;
  
  		case TSP_TRACEON:
  			traceon();
  			break;
  
  		case TSP_TRACEOFF:
  			traceoff("Tracing ended at %s\n");
  			break;
  
  		case TSP_SLAVEUP:
  			newslave(msg);
  			break;
  
  		case TSP_ELECTION:
  			if (fromnet->status == SLAVE) {
  				(void)gettimeofday(&ntime, 0);
  				electiontime = ntime.tv_sec + delay2;
  				fastelection = ntime.tv_sec + FASTTOUT;
  				seq = 0;
  				if (!good_host_name(msg->tsp_name)) {
  					syslog(LOG_NOTICE,
  					       "suppress election of %s",
  					       msg->tsp_name);
  					to.tsp_type = TSP_QUIT;
  					electiontime = fastelection;
  				} else if (cadr.s_addr != from.sin_addr.s_addr
  					   && ntime.tv_sec < refusetime) {
  /* if the candidate has to repeat itself, the old code would refuse it
   * the second time.  That would prevent elections.
   */
  					to.tsp_type = TSP_REFUSE;
  				} else {
  					cadr.s_addr = from.sin_addr.s_addr;
  					to.tsp_type = TSP_ACCEPT;
  					refusetime = ntime.tv_sec + 30;
  				}
  				taddr = from;
  				(void)strcpy(tname, msg->tsp_name);
  				(void)strcpy(to.tsp_name, hostname);
  				answerdelay();
  				if (!acksend(&to, &taddr, tname,
  					     TSP_ACK, 0, 0))
  					syslog(LOG_WARNING,
  					     "no answer from candidate %s\n",
  					       tname);
  
  			} else {	/* fromnet->status == MASTER */
  				htp = addmach(msg->tsp_name, &from,fromnet);
  				to.tsp_type = TSP_QUIT;
  				(void)strcpy(to.tsp_name, hostname);
  				if (!acksend(&to, &htp->addr, htp->name,
  					     TSP_ACK, 0, htp->noanswer)) {
  					syslog(LOG_ERR,
  					  "no reply from %s to ELECTION-QUIT",
  					       htp->name);
  					(void)remmach(htp);
  				}
  			}
  			break;
  
  		case TSP_CONFLICT:
  			if (fromnet->status != MASTER)
  				break;
  			/*
  			 * After a network partition, there can be
  			 * more than one master: the first slave to
  			 * come up will notify here the situation.
  			 */
  			(void)strcpy(to.tsp_name, hostname);
  
  			/* The other master often gets into the same state,
  			 * with boring results.
  			 */
  			ntp = fromnet;	/* (acksend() can leave fromnet=0 */
  			for (tries = 0; tries < 3; tries++) {
  				to.tsp_type = TSP_RESOLVE;
  				answer = acksend(&to, &ntp->dest_addr,
  						 ANYADDR, TSP_MASTERACK,
  						 ntp, 0);
  				if (answer == NULL)
  					break;
  				htp = addmach(answer->tsp_name,&from,ntp);
  				to.tsp_type = TSP_QUIT;
  				answer = acksend(&to, &htp->addr, htp->name,
  						 TSP_ACK, 0, htp->noanswer);
  				if (!answer) {
  					syslog(LOG_WARNING,
  				  "conflict error: no reply from %s to QUIT",
  						htp->name);
  					(void)remmach(htp);
  				}
  			}
  			masterup(ntp);
  			break;
  
  		case TSP_MSITE:
  			if (!slavenet)
  				break;
  			taddr = from;
  			to.tsp_type = TSP_MSITEREQ;
  			to.tsp_vers = TSPVERSION;
  			to.tsp_seq = 0;
  			(void)strcpy(to.tsp_name, hostname);
  			answer = acksend(&to, &slavenet->dest_addr,
  					 ANYADDR, TSP_ACK,
  					 slavenet, 0);
  			if (answer != NULL
  			    && good_host_name(answer->tsp_name)) {
  				setmaster(answer);
  				to.tsp_type = TSP_ACK;
  				(void)strcpy(to.tsp_name, answer->tsp_name);
  				bytenetorder(&to);
  				if (sendto(sock, (char *)&to,
  					   sizeof(struct tsp), 0,
  					   (struct sockaddr*)&taddr, sizeof(taddr)) < 0) {
  					trace_sendto_err(taddr.sin_addr);
  				}
  			}
  			break;
  
  		case TSP_MSITEREQ:
  			break;
  
  		case TSP_ACCEPT:
  		case TSP_REFUSE:
  		case TSP_RESOLVE:
  			break;
  
  		case TSP_QUIT:
  			doquit(msg);		/* become a slave */
  			break;
  
  		case TSP_TEST:
  			electiontime = 0;
  			break;
  
  		case TSP_LOOP:
  			/* looking for loops of masters */
  			if (!(status & MASTER))
  				break;
  			if (fromnet->status == SLAVE) {
  			    if (!strcmp(msg->tsp_name, hostname)) {
  				/*
  				 * Someone forwarded our message back to
  				 * us.  There must be a loop.  Tell the
  				 * master of this network to quit.
  				 *
  				 * The other master often gets into
  				 * the same state, with boring results.
  				 */
  				ntp = fromnet;
  				for (tries = 0; tries < 3; tries++) {
  				    to.tsp_type = TSP_RESOLVE;
  				    answer = acksend(&to, &ntp->dest_addr,
  						     ANYADDR, TSP_MASTERACK,
  						     ntp,0);
  				    if (answer == NULL)
  					break;
  				    taddr = from;
  				    (void)strcpy(tname, answer->tsp_name);
  				    to.tsp_type = TSP_QUIT;
  				    (void)strcpy(to.tsp_name, hostname);
  				    if (!acksend(&to, &taddr, tname,
  						 TSP_ACK, 0, 1)) {
  					syslog(LOG_ERR,
  					"no reply from %s to slave LOOP-QUIT",
  						 tname);
  				    } else {
  					electiontime = 0;
  				    }
  				}
  				(void)gettimeofday(&ntime, 0);
  				looptime = ntime.tv_sec + FASTTOUT;
  			    } else {
  				if (msg->tsp_hopcnt-- < 1)
  				    break;
  				bytenetorder(msg);
  				for (ntp = nettab; ntp != 0; ntp = ntp->next) {
  				    if (ntp->status == MASTER
  					&& 0 > sendto(sock, (char *)msg,
  						      sizeof(struct tsp), 0,
  					      (struct sockaddr*)&ntp->dest_addr,
  						      sizeof(ntp->dest_addr)))
  				    trace_sendto_err(ntp->dest_addr.sin_addr);
  				}
  			    }
  			} else {	/* fromnet->status == MASTER */
  			    /*
  			     * We should not have received this from a net
  			     * we are master on.  There must be two masters,
  			     * unless the packet was really from us.
  			     */
  			    if (from.sin_addr.s_addr
  				== fromnet->my_addr.s_addr) {
  				if (trace)
  				    fprintf(fd,"discarding forwarded LOOP\n");
  				break;
  			    }
  
  			    /*
  			     * The other master often gets into the same
  			     * state, with boring results.
  			     */
  			    ntp = fromnet;
  			    for (tries = 0; tries < 3; tries++) {
  				to.tsp_type = TSP_RESOLVE;
  				answer = acksend(&to, &ntp->dest_addr,
  						 ANYADDR, TSP_MASTERACK,
  						ntp,0);
  				if (!answer)
  					break;
  				htp = addmach(answer->tsp_name,
  					      &from,ntp);
  				to.tsp_type = TSP_QUIT;
  				(void)strcpy(to.tsp_name, hostname);
  				if (!acksend(&to,&htp->addr,htp->name,
  					     TSP_ACK, 0, htp->noanswer)) {
  					syslog(LOG_ERR,
  				    "no reply from %s to master LOOP-QUIT",
  					       htp->name);
  					(void)remmach(htp);
  				}
  			    }
  			    (void)gettimeofday(&ntime, 0);
  			    looptime = ntime.tv_sec + FASTTOUT;
  			}
  			break;
  		default:
  			if (trace) {
  				fprintf(fd, "garbage message: ");
  				print(msg, &from);
  			}
  			break;
  		}
  	}
  	goto loop;
  }
  
  
  /*
   * tell the world who our master is
   */
  static void
  setmaster(msg)
  	struct tsp *msg;
  {
  	if (slavenet
  	    && (slavenet != old_slavenet
  		|| strcmp(msg->tsp_name, master_name)
  		|| old_status != status)) {
  		(void)strcpy(master_name, msg->tsp_name);
  		old_slavenet = slavenet;
  		old_status = status;
  
  		if (status & MASTER) {
  			syslog(LOG_NOTICE, "submaster to %s", master_name);
  			if (trace)
  				fprintf(fd, "submaster to %s\n", master_name);
  
  		} else {
  			syslog(LOG_NOTICE, "slave to %s", master_name);
  			if (trace)
  				fprintf(fd, "slave to %s\n", master_name);
  		}
  	}
  }
  
  
  
  /*
   * handle date change request on a slave
   */
  static void
  schgdate(msg, newdate)
  	struct tsp *msg;
  	char *newdate;
  {
  	struct tsp to;
  	u_short seq;
  	struct sockaddr_in taddr;
  	struct timeval otime;
  
  	if (!slavenet)
  		return;			/* no where to forward */
  
  	taddr = from;
  	seq = msg->tsp_seq;
  
  	syslog(LOG_INFO,
  	       "forwarding date change by %s to %s",
  	       msg->tsp_name, newdate);
  
  	/* adjust time for residence on the queue */
  	(void)gettimeofday(&otime, 0);
  	adj_msg_time(msg, &otime);
  
  	to.tsp_type = TSP_SETDATEREQ;
  	to.tsp_time = msg->tsp_time;
  	(void)strcpy(to.tsp_name, hostname);
  	if (!acksend(&to, &slavenet->dest_addr,
  		     ANYADDR, TSP_DATEACK,
  		     slavenet, 0))
  		return;			/* no answer */
  
  	xmit(TSP_DATEACK, seq, &taddr);
  }
  
  
  /*
   * Used before answering a broadcast message to avoid network
   * contention and likely collisions.
   */
  static void
  answerdelay()
  {
  #ifdef sgi
  	sginap(delay1);
  #else
  	struct timeval timeout;
  
  	timeout.tv_sec = 0;
  	timeout.tv_usec = delay1;
  
  	(void)select(0, (fd_set *)NULL, (fd_set *)NULL, (fd_set *)NULL,
  	    &timeout);
  	return;
  #endif /* sgi */
  }
  