OpenBSD-4.6/usr.sbin/timed/timed/timed.c
/* $OpenBSD: timed.c,v 1.28 2008/03/17 16:29:25 sobrado Exp $ */
/*-
* 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. 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.
*/
#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1985, 1993 The Regents of the University of California.\n\
All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)timed.c 5.1 (Berkeley) 5/11/93";
#endif /* not lint */
#define TSPTYPES
#include "globals.h"
#include <net/if.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <setjmp.h>
#include "pathnames.h"
#include <math.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/times.h>
#include <netgroup.h>
#include <err.h>
#include <ifaddrs.h>
int trace = 0;
int sock, sock_raw = -1;
int status = 0;
u_short sequence; /* sequence number */
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 */
jmp_buf jmpenv;
volatile sig_atomic_t gotintr;
struct netinfo *nettab = 0;
struct netinfo *slavenet;
int Mflag;
int justquit = 0;
int debug;
struct nets {
char name[1024];
in_addr_t net;
TAILQ_ENTRY(nets) next;
};
static TAILQ_HEAD(, nets) nets;
struct hosttbl hosttbl[NHOSTS+1]; /* known hosts */
/* List of hosts we trust */
struct goodhost {
char name[MAXHOSTNAMELEN];
int perm;
TAILQ_ENTRY(goodhost) next;
};
static TAILQ_HEAD(, goodhost) goodhosts;
static char *goodgroup; /* net group of trusted hosts */
/* prototypes */
static void addnetname(const char *);
static void checkignorednets(void);
static void pickslavenet(struct netinfo *);
static void add_good_host(const char *, int);
static void usage(void);
/*
* 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;
struct netinfo *ntp;
struct netinfo *ntip;
struct netinfo *savefromnet;
struct nets *nt;
struct sockaddr_in server;
u_short port;
int ch;
struct ifaddrs *ifap, *ifa;
ntip = NULL;
on = 1;
nflag = 0;
iflag = 0;
TAILQ_INIT(&nets);
TAILQ_INIT(&goodhosts);
opterr = 0;
while ((ch = getopt(argc, argv, "F:G:Mdi:n:t")) != -1) {
switch (ch) {
case 'F':
add_good_host(optarg, 1);
while (optind < argc && argv[optind][0] != '-')
add_good_host(argv[optind++], 1);
break;
case 'G':
if (goodgroup != NULL) {
fprintf(stderr,"timed: only one net group\n");
exit(1);
}
goodgroup = optarg;
break;
case 'M':
Mflag = 1;
break;
case 'd':
debug = 1;
break;
case 'i':
iflag = 1;
addnetname(optarg);
break;
case 'n':
nflag = 1;
addnetname(optarg);
break;
case 't':
trace = 1;
break;
default:
usage();
/* NOTREACHED */
}
}
if (optind < argc)
usage();
if (nflag && iflag) {
fprintf(stderr, "timed: -i and -n make no sense together\n");
exit(1);
}
/*
* If we care about which machine is the master, then we must be
* willing to be a master as well.
*/
if ((goodgroup != NULL) || !TAILQ_EMPTY(&goodhosts))
Mflag = 1;
if (gethostname(hostname, sizeof(hostname)) < 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;
/* Add ourselves to the list of trusted hosts */
if (!TAILQ_EMPTY(&goodhosts))
add_good_host(hostname, 1);
if ((srvp = getservbyname("timed", "udp")) == NULL) {
fprintf(stderr, "unknown service 'timed/udp'\n");
exit(1);
}
port = srvp->s_port;
bzero(&server, sizeof(server));
server.sin_port = srvp->s_port;
server.sin_family = AF_INET;
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);
}
sequence = arc4random(); /* initial seq number */
gettimeofday(&ntime, 0);
/* 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);
TAILQ_FOREACH(nt, &nets, next) {
struct netent *nentp;
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);
}
if (0 == (nt->net & 0xff000000))
nt->net <<= 8;
if (0 == (nt->net & 0xff000000))
nt->net <<= 8;
if (0 == (nt->net & 0xff000000))
nt->net <<= 8;
}
if (getifaddrs(&ifap) != 0) {
perror("timed: get interface configuration");
exit(1);
}
ntp = NULL;
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_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 *)ifa->ifa_addr)->sin_addr;
ntp->status = NOMASTER;
if ((ifa->ifa_flags & IFF_UP) == 0)
continue;
if ((ifa->ifa_flags & IFF_BROADCAST) == 0 &&
(ifa->ifa_flags & IFF_POINTOPOINT) == 0) {
continue;
}
((struct sockaddr_in *)ifa->ifa_addr)->sin_addr = ntp->my_addr;
ntp->mask = ((struct sockaddr_in *)
ifa->ifa_netmask)->sin_addr.s_addr;
if (ifa->ifa_flags & IFF_BROADCAST) {
ntp->dest_addr = *(struct sockaddr_in *)ifa->ifa_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 {
ntp->dest_addr = *(struct sockaddr_in *)ifa->ifa_dstaddr;
ntp->net = ntp->dest_addr.sin_addr;
}
ntp->dest_addr.sin_port = port;
TAILQ_FOREACH(nt, &nets, next) {
if (ntohl(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);
}
freeifaddrs(ifap);
/* election timer delay in secs. */
delay2 = casual(MINTOUT, MAXTOUT);
if (!debug)
daemon(debug, 0);
if (trace)
traceon();
openlog("timed", LOG_CONS|LOG_PID, LOG_DAEMON);
/*
* keep returning here
*/
ret = setjmp(jmpenv);
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, const 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;
strlcpy(tname, name, sizeof(tname));
while (readmsg(TSP_ANY, ANYADDR, &wait, net) != NULL) {
if (trace)
fprintf(fd, "suppress:\tdiscarded packet from %s\n",
name);
}
syslog(LOG_NOTICE, "suppressing false master %s", tname);
memset(&msg, 0, sizeof(msg));
msg.tsp_type = TSP_QUIT;
strlcpy(msg.tsp_name, hostname, sizeof msg.tsp_name);
(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 */
memset(&resp, 0, sizeof(resp));
resp.tsp_type = TSP_MASTERREQ;
strlcpy(resp.tsp_name, hostname, sizeof(resp.tsp_name));
answer = acksend(&resp, &ntp->dest_addr, ANYADDR,
TSP_MASTERACK, ntp, 0);
if ((answer != NULL) && !good_host_name(answer->tsp_name)) {
suppress(&from, answer->tsp_name, ntp);
ntp->status = NOMASTER;
answer = 0;
}
if (answer == NULL) {
/*
* 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 != NULL) {
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 != NULL) {
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 != NULL) {
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;
strlcpy(mastername, answer->tsp_name, sizeof mastername);
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;
strlcpy(conflict.tsp_name, hostname, sizeof conflict.tsp_name);
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)
{
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)
{
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)
{
return (inf + random() % (sup - inf + 1));
}
char *
date(void)
{
struct timeval tv;
time_t t;
(void)gettimeofday(&tv, (struct timezone *)0);
t = tv.tv_sec;
return (ctime(&t));
}
void
addnetname(const char *name)
{
struct nets *netlist;
if ((netlist = (struct nets *)calloc(1, sizeof(*netlist))) == NULL)
err(1, "malloc");
strlcpy(netlist->name, name, sizeof(netlist->name));
TAILQ_INSERT_TAIL(&nets, netlist, next);
}
/*
* add_good_host() -
*
* Add a host to our list of trusted hosts.
*/
static void
add_good_host(const char *name, int perm)
{
struct goodhost *ghp;
struct hostent *hentp;
if ((ghp = (struct goodhost *)calloc(1, sizeof(*ghp))) == NULL)
err(1, "malloc");
strlcpy(ghp->name, name, sizeof(ghp->name));
ghp->perm = perm;
TAILQ_INSERT_TAIL(&goodhosts, ghp, next);
if ((hentp = gethostbyname(name)) == NULL && perm)
(void)fprintf(stderr, "unknown host %s\n", name);
}
/* update our image of the net-group of trustworthy hosts
*/
void
get_goodgroup(int force)
{
# define NG_DELAY (30*60*CLK_TCK) /* 30 minutes */
static unsigned long last_update = -NG_DELAY;
unsigned long new_update;
struct hosttbl *htp;
struct goodhost *ghp, *nxt;
const char *mach, *usr, *dom;
struct tms tm;
/* 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 */
for (ghp = TAILQ_FIRST(&goodhosts); ghp != NULL; ghp = nxt) {
nxt = TAILQ_NEXT(ghp, next);
if (!ghp->perm) {
TAILQ_REMOVE(&goodhosts, ghp, next);
free(ghp);
}
}
/* 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]);
}
}
/* see if a machine is trustworthy
*/
int /* 1=trust hp to change our date */
good_host_name(const char *name)
{
struct goodhost *ghp;
if (TAILQ_EMPTY(&goodhosts) || !Mflag)
return (1);
TAILQ_FOREACH(ghp, &goodhosts, next) {
if (strcasecmp(ghp->name, name) == 0)
return (1);
}
/* XXX - Should be no need for this since we already added ourselves */
if (strcasecmp(name, hostname) == 0)
return (1);
return (0);
}
static void
usage(void)
{
(void)fprintf(stderr, "usage: timed [-dMt] [-F host ...] [-G netgroup] "
"[-i network | -n network]\n");
exit(1);
}