#define DATE	"10 September, 1993"

/*

WATCHER -- an IP-connected host monitoring program.

(Tom Jennings) tomj@wps.com
written 29 June 93


SYNOPSYS:

Given a list of network hosts, and other parameters, checks periodically and
reports on any hosts that stop responding or become responsive again. "Actions"
(commands) may be executed upon a hosts status change.



consists of:
	watcher.c	(this source)
	/etc/watcher.conf	(the configuration file, lots of comments)
	watcher.1	(a man page)

-------------- And now a short word from our sponsor --------------

This program and source may be freely used and distributed, under
the condition it is not used for commercial purposes. eg. you can't
sell it or install as system software in for-sale systems. However,
permission is given to distribute it on for-sale media as long as
there are at least 99 other differnt programs on the same media.
If you want to use it commercially, just ask.  I'm easy.

In other words, this work copyright Tom Jennings 1993, San Francisco
Calif. tomj@wps.com

-------------------------------------------------------------------


BUILDING WATCHER:

This was originally written on 386BSD (Jolitz) 0.18, and also on BSDI's
386/BSD or is that BSD/386. It's as clean and portable as I know how to
at this point, as I'm new to this unix stuff, all those pipes and
things. 

cc watcher.c			#(yup, I do it by hand)
mv a.out watcher		#(so I'm lazy, sue me)
chown root watcher		#(assumes you're root, of course)
chmod u+s watcher		#(must run SUID)
mv watcher /usr/local/bin

# Yup, a real man page. Don't worry, it's not very good.

nroff -mandoc watcher.1 > /usr/local/bin/watcher.0

Given a list of hosts (in watcher.conf, blank lines and # ignored),
pings each host once per [test interval], staggered evenly through
each interval to avoid a flood of pings.  Replies to these pings
are tallied for each host in an internal table. Systems that do
not respond within [test limit] seconds are marked as "down"; if
a previously-deceased host starts responding again, it is marked
"alive".

The change from dead to/from alive, and downtime and uptime are
recorded for each host.  Each dead to/from alive change generates
a new status report for that host.

A report is generated [test limit] seconds after the program is
started, and a final report is generated when the program is
terminated with a Control-C.

Internally, the program sleeps most of the time, with an SIGALARM-driven
ping source, and block-on-read awaiting for reply datagrams with
a 10 second time out. Raw datagrams are used.


ACTIONS:

When a host changes state, a report is generated. If an action is
specified, the report is written to a temp file, one per action.
Reports are accumulated in this file, until [test_interval] time
passes since the last update; the action itself, processing the
report data (such as mailing it) is done. This avoids a flurry of
one-line reports when a number of hosts using the same action change
state within a short period of time (such as when the program is
first run).

CAVEATS: (31 Aug 93)

The uptime/downtime thing is broken. It's also of dubious use right now,
as it was not well thought out. Obviously the stats are only useful
commencing from when the program is started. What does "up time" mean?
What does "down time" mean?

One idea would be to keep stats modulo some longish period, day, week,
month... maybe someday I'll get inspired (hold your breath starting
now...)

So the code looks authoritative, seemingly calculating up and down
times, but downtime is broken, and none of it's used.

*/

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/signal.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_var.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>


/* ----- The following are read or determined from the init file,
and have the following defaults ----- */


int test_interval = (1 * 60);	/* test all hosts every 1 minute */
int test_limit = (5 * 60);

struct _host {			/* list of hosts to test */
	char *name;		/* name of host */
	struct in_addr addr;	/* it's IP address */
	struct sockaddr_in socket;/* socket we're connected with */
	int socket_handle;	/* socket handle */
	time_t start_time;	/* when we started counting */
	time_t ping_time;	/* last time we pinged */
	time_t resp_time;	/* last time we got a response */
	long downtime;		/* total downtime, seconds */
	char tag;		/* letter tag of action or 0 */
	char alive;		/* 1 == host has responded within (test_limit) */
	char dead;		/* 1 == host has not responded */

};
struct _host *host = NULL;	/* ptr to grown list of hosts */
int hostn = 0;			/* count derived from reading in hosts */


/* Table of actions. */

struct _action {
	char tag;		/* action name, A - Z */
	char *command;		/* command string to exec */
	time_t write_time;	/* time of last report */
};
struct _action *action = NULL;	/* ptr to grown list of actions */
int actionn = 0;


/* ----- End of junk read from file. ----- */

/* Global, shared junk. */

int ident;			/* our program ID */
struct icmp icmp_send;		/* the ICMP packet we send */
struct protoent *proto;
unsigned last_sent;		/* host # last pinged */
char buff[200];			/* read data from socket */



int
main(argc,argv)
int argc;
char *argv[];
{
int cc;
int i,s;
char *cp;
char state;			/* kludge to get test limit, test interval from conf file */
char a;				/* action tag peeled off hostname */
int next_report;		/* reporter */
FILE *f;			/* config file */
struct hostent *hp;		/* info returned by getbyhostname() */
struct servent *sp;		/* info returned by getservbyname() */
extern int stopper();		/* our interrupt handler */
time_t t;

        if (!(proto = getprotobyname("icmp"))) {
                (void)fprintf(stderr, "unknown protocol icmp.\n");
                exit(1);
        }
	ident = getpid() & 0xFFFF;		/* our program ID to identify packets */


/* Set up the outgoing ICMP ping packet now (it never varies). */

	icmp_send.icmp_type = ICMP_ECHO;
	icmp_send.icmp_code = 0;
	icmp_send.icmp_cksum = 0;
	icmp_send.icmp_seq = 0;
	icmp_send.icmp_id = ident;			/* our process ID */
	icmp_send.icmp_cksum = in_cksum((u_short *)&icmp_send, sizeof(struct icmp));

/* Read the config file and load in all the hostnames and actions. As we build the host table,
we have to grow it, as well as space for the hostname strings. */

	if ((f= fopen("/etc/watcher.conf","r")) == NULL) {
                (void)fprintf(stderr, "Can't find config file \"watcher.conf\"\n");
		exit(1);
	} 

	for (state= hostn= 0;; ) {			/* read each name, */
		if (fgets(buff,sizeof(buff),f) == NULL) break;
		for (cp= buff; *cp; ++cp) 		/* strip newlines */
			if (*cp == '\n') *cp= '\0';
		if (! buff[0]) continue;		/* ignore blank lines */
		if (buff[0] == '#') continue;		/* ignore comments */

/* KLUDGE: the first two lines define the test interval and test limit. No niceties. We
test the values for reasonableness, after we know the number of hosts to test. */

		switch (state) {			/* KLUDGE */
			case 0:	 test_interval= atoi(buff); ++state; continue; 
			case 1: test_limit= atoi(buff); ++state; continue;
		}

/* If the second character is a colon, it's an action definition; the rest of the
line is the command to execute, and the character before the colon is the name (tag) for
this command. */

		if (buff[1] == ':') {			/* if it's an action, */
			if ((action= (struct _action *)realloc(action, ((actionn + 1) * sizeof(struct _action)) )) == NULL) {
				fprintf(stderr,"(action) realloc claims we're out of memory!\n");
				exit(1);
			}
			cp= &buff[2];			/* skip "a:" */
			if ((action[actionn].command= (char *)malloc(strlen(cp) + 1)) == NULL) {
				fprintf(stderr,"(action) malloc sez we're outa memory!\n");
				exit(1);
			}
			action[actionn].tag= buff[0];	/* name of this action */
			action[actionn].write_time= 0L;	/* not written to */
			strcpy(action[actionn++].command, cp);
			continue;			/* neeeeext... */
		}

/* If it's not an action definition, it's a hostname. If the hostname is followed immediately
by =a, assume this is the action to execute for this hostname. We're not real tolerant on
line format. No whitespace is allowed. */

		for (a= '!', cp= buff; *cp; ++cp) {	/* look for trailing '=' */
			if (*cp == '=') {		/* indicating an action */
				a= cp[1];		/* remember it */
				*cp= '\0';		/* clip off =a... */
			}
		}
		if ((host= (struct _host *)realloc(host, ((hostn + 1) * sizeof(struct _host)) )) == NULL) {
			fprintf(stderr,"(host) realloc claims we're out of memory!\n");
			exit(1);
		}
		if ((host[hostn].name= (char *)malloc(strlen(buff) + 1)) == NULL) {
			fprintf(stderr,"malloc sez we're outa memory!\n");
			exit(1);
		}
		host[hostn].tag= a;			/* remember (possible) action */
		strcpy(host[hostn++].name,buff);	/* copy in host name */
	}
	fclose(f);

/* Make sure that all the action tags specified for each hostname actually exists in the
action table! */

	for (cc= i= 0; i < hostn; i++) {		/* for each host... */
		if (a= host[i].tag) {			/* if action specified */
			if (a == '!') continue;		/* definition for ! not required */
			for (s= 0; s < actionn; s++) 	/* look through the action table */
				if (a == action[s].tag) break;
			if (s == actionn) { 		/* if not found */
				fprintf(stderr,"action \"%c\" for host %s not defined!\n",
				    a,host[i].name);
				cc= 1;			/* flag as error */
			}
/* too verbose!		} else fprintf(stderr,"host \"%s\", action: %s\n",host[i].name, 
			    action[s].command);
*/
		}
	}

/* Anything to do? */

	if (hostn == 0) {
		fprintf(stderr, "No hosts to test!\n");
		cc= 1;					/* flag the error */
		hostn= 1;				/* avoid divide-by-zero */
	}

/* Now sanity check the test interval and limit, knowing the number of hosts we have to
ping. Since we ping hostn addresses per test_interval, make sure this is even possible. */

	if ((test_interval < 1) || (test_limit < 1)) {
		fprintf(stderr, "Test limit and test interval are specified as integer \n");
		fprintf(stderr, "seconds, greater than 0\n");
		exit(1);
	}
	if (test_interval / hostn < 1) {		/* < 1 sec/ping! */
		fprintf(stderr,"Testing %d hosts every %d seconds won't work, must be \n",
		    hostn, test_interval);
		fprintf(stderr, "1 per second maximum\n");
		exit(1);
	}
	if (test_limit < test_interval) {
		fprintf(stderr, "Since you said the test interval is %d seconds, ", test_interval);
		fprintf(stderr, "and the (alive) test limit is %d seconds,\n", test_limit);
		fprintf(stderr, "this requires that hosts respond before they are tested. ");
		fprintf(stderr, "This is most unreasonable! I quit!\n");
		exit(1);
	}
	if (test_interval < 10) {
		fprintf(stderr, "WARNING: Do you really want to test each system every %d seconds?!\n",test_interval);
	}
	if (test_interval > 1440 * 60) {
		fprintf(stderr, "WARNING: Testing once per day or less seem less than useful, but ");
		fprintf(stderr, "humans always know what they are doing.\n");
	}
	if (test_interval / hostn < 2) {
		fprintf(stderr, "WARNING: the programmer considers testing %d hosts in ", hostn);
		fprintf(stderr, "%d seconds excessive, \nbut what do I know.\n", test_interval);
	}
	if (test_limit < test_interval * 2) {
		fprintf(stderr, "WARNING: Generally, the test-limit should be twice or more times the\n");
		fprintf(stderr, "test-interval, to allow for the occasional lost ICMP packet (since ICMP\n");
		fprintf(stderr, "packets use UDP they are not resent if damaged in-transit).\n");
	}

	if (cc) exit(1);				/* stop here if previous error */


/* For each host in the table, look it up in DNS or /etc/hosts, get a socket to
connect with, and fill in the socket info from the looked-up host info. If we find errors,
don't quit until we've reported them all. There's nothing more annoying than programs that
don't show you all the errors at once. */

	fprintf(stderr,"Read %d host names, %d actions, looking up addresses...\n",hostn, actionn);
	s= 0;						/* use s as error flag */
	for (i= 0; i < hostn; i++) {			/* for each host in the list... */
		if (inet_aton(host[i].name,&host[i].addr)) { /* if a numeric address */
			bcopy(&host[i].addr, &host[i].socket.sin_addr, sizeof(struct in_addr));

		} else if ((hp= gethostbyname(host[i].name)) != NULL) {
			bcopy(hp-> h_addr, &host[i].socket.sin_addr, hp-> h_length); /* copy in inet addr */
			bcopy(hp-> h_addr, &host[i].addr, hp-> h_length); 

		} else { 
        		fprintf(stderr,"Ain't no such host \"%s\"\n", host[i].name);
			++s; continue;			/* oops! */
		}
		if ((host[i].socket_handle = socket(AF_INET,SOCK_RAW, proto-> p_proto)) < 0) { 
			perror("watcher: socket");
			exit(1); 			/* OOPS! */
		}
		time(&host[i].start_time);		/* when we started */
		host[i].socket.sin_family= AF_INET;	/* setup the socket data */
		host[i].socket.sin_port= 0;		/* set port number */

		time(&host[i].resp_time);		/* let's assume its alive now */
		host[i].ping_time= host[i].resp_time;
		host[i].alive= host[i].dead= 0;		/* unknown state */
		host[i].downtime= 0L;
	}
	if (s) exit(1);					/* some damn thing went wrong */
	say_hello();					/* the hello report */
	signal(SIGINT,(void *)stopper);			/* our Control-C handler */
	pinger();					/* start the first ping */

/* This is the main loop. pinger() runs asynchronously, pinging each host in the host
table once per test_interval. Echo-reply datagrams come in more or less randomly; here
we look for and read ICMP datagrams (get_reply()) and check them for originating with us
(pid, ECHOREPLY and host address checked in check_is_ours() if if correct, the host table
entry last-response time is updated. 

The status reporter looks for hosts whose last-response time falls outside the test_limit
window, and detect dead (response within limit -> outside limit) and alive (outside limit
-> within limit).

get_reply() blocks on read, so that we sleep most of the time. */

	for (next_report= 0;;) {
		time(&t);				/* time of this read */
		status(t,next_report);			/* report current status */
		do_action(t, test_interval);		/* act on reports */
		if (++next_report >= hostn) next_report= 0;

		if (i= get_reply(buff, sizeof(buff)))
			 if ((i= check_is_ours(buff, i, &host[0].socket)) != -1)
				host[i].resp_time= t;
	}
}

/* If there is an ICMP datagram waiting for us read it, with appropriate checks. Note that
we can read from any socket, as all sockets receive raw datagrams. Returns the size of the
datagram read, else 0. */

get_reply(buff, bufflen)
char *buff;		/* where to read datagram to */
int bufflen;            /* how long it is */
{
int cc;
int i,s;
fd_set readfds;			/* select() read file descriptors */
struct timeval timeout;		/* timeout for select() */


	FD_ZERO(&readfds);
	FD_SET(s= host[0].socket_handle, &readfds);	/* choose which read file descr */
	timeout.tv_sec= 10;
	timeout.tv_usec= 0;
	if (select(s + 1, &readfds, (fd_set *)NULL, 
	  (fd_set *)NULL, &timeout) < 1) 		/* nothing to read */
		return(0);

	i= sizeof(struct sockaddr_in);			/* (for recvfrom()) */
	if ((cc = recvfrom(s, (void *)buff, bufflen, 0, 
	    (struct sockaddr *)&host[0].socket, &i)) >= 0)
		return(cc);				/* success! */
 
	if (errno != EINTR) perror("watcher: recvfrom");/* error unless interrupted by sig */
	return(0);					/* in any case, nothing read */
}

/* Report and status change on this host. This checks for up/dn
and dn/up status changes. If we exceed the test_limit time, report
that this host is dead and tally the down time (once). When it
comes alive, report it as alive again. 

The dual dead/alive flags allows proper reporting on startup
without resort to kludges. !dead || !alive means state is unknown. */

status(t,i)
time_t t;	/* current time */
int i;		/* host[i] */
{
char *dead, *alive;

	if (t - host[i].start_time <= test_limit) return;

	if (! (host[i].alive || host[i].dead)) {
		dead= "is currently not responding"; alive= "is responding";

	} else {
		dead= "stopped responding"; alive= "started responding again";
	}

	if (t - host[i].resp_time < test_limit) {
		if (! host[i].alive) report(i,alive);
		host[i].alive= 1;		/* flag as alive */
		host[i].dead= 0;		/* flag as not dead */

	} else {				/* .GE. limit */
		if (! host[i].dead) {
			host[i].dead= 1;	/* is dead */
			host[i].alive= 0;	/* not alive */
			host[i].downtime += (host[i].ping_time - host[i].resp_time);
			report(i,dead);
		}
	}
}

/* Interrupt key handler. */

stopper() {
int i;
time_t t;

	fprintf(stderr,"\nINTERRUPT\n");
/*	for (i= 0; i < hostn; i++) {
		host[i].alive= host[i].dead= 0;
		report(i,"");
	}
	time(&t);
	do_action(t,0);
*/	fprintf(stderr,"Watcher stopped.\n");
	exit(0);
}

/* Compose and send an ICMP ECHO REQUEST datagram to each host in the table. This
sends a datagram to each host once per test interval. Since sending one datagram each
to a long list of hosts would make for an obnoxious flood, this sends one ping per
(test interval / number of hosts) unit time. We don't check for sequence or dups, since
all we care about is an alive host; any response will do. */

pinger() {
register int cc;
int i;

	if (last_sent >= hostn) last_sent= 0;	/* bound it (NOTE: IS UNINITIALIZED!) */
	time(&host[last_sent].ping_time);	/* when we last tried to talk */

/* The ICMP packet is setup once in main() as it never varies. */

	if ((i= sendto(host[last_sent].socket_handle, (char *)&icmp_send, sizeof(struct icmp),
	    0, (struct sockaddr *)&host[last_sent].socket, sizeof(struct sockaddr))) < 0) 
		perror("watcher: sendto");	/* socket write error */

	++last_sent;				/* sent OK; next... */
        
	if ((int)signal(SIGALRM,(void *)pinger) == -1) {/* we'll handle it */
		perror("watcher:");
		exit(1);
	}
        alarm(test_interval / hostn);		/* go off again in this many seconds */
}

/* See if this datagram is for one of our hosts; if so return it's index else -1. */

check_is_ours(buf, cc, from)
char *buf;			/* where the crud is located */
int cc;				/* how much crud there is */
struct sockaddr_in *from;	/* the socket it dripped out of */
{
struct ip *ip;			/* ptr to the IP header in the datagram */
struct icmp *icp;		/* ptr to the ICMP data within the datagram */
int hlen;			/* length of the IP header (where ICMP starts) */
int i;

	ip = (struct ip *)buf;
	hlen = ip->ip_hl << 2;				/* gack! a cheat! */
	if (cc < hlen + ICMP_MINLEN) {
		(void)fprintf(stderr,
		  "watcher: datagram too short (%d bytes) from %s\n", cc,
		  inet_ntoa(*(struct in_addr *)&from->sin_addr.s_addr));
		return(-1);
	}

/* Now check that this is an ICMP packet generated by us. */

	icp = (struct icmp *)(buf + hlen);
	if ((icp->icmp_type != ICMP_ECHOREPLY) || (icp->icmp_id != ident)) 
		return(-1);					/* not echoreply or our PID */

/* It's from our PID; locate the IP address in the host table. */

	for (i= 0; i < hostn; i++) {
		if (!bcmp((char *)&from-> sin_addr.s_addr,	/* addr in datagram */
		    (char *)&host[i].addr,			/* addr in host table */
		    sizeof(struct in_addr))) 
			return(i);				/* yup it's ours */
	}
	return(-1);
}

/* Generate a status report for host[h]. The report data is written to a file
unique to each action. */

report(h,s)
int h;		/* host[h] */
char *s;	/* string to add */
{
char *cp,*sp;
char up[40],dn[40],buff[200];
int a, i, o;
FILE *f;

/* We don't currently use the up/down time. I hate that ANSI C doesn't allow nested comments,
hate hate hate I wish they would all die. Or at least change it. */

/*	lstos(up, host[h].resp_time - host[h].start_time); */	 /* stringize up and down times */
/*	lstos(dn, host[h].downtime);			*/

	sp= asctime(localtime(&host[h].resp_time));		/* "Thu Jul 15 18:00:02 1993" */
	sp[19]= '\0';						/* clip off " 1993" */
	sp= &sp[4];						/* clip off "Thu " */

/*	sprintf(buff,"%s %-30s (up %5s, dn %5s) %s\n", sp,  host[h].name, up, dn, s); */


	sprintf(buff,"%s %-30s %s\n", sp,  host[h].name, s);
	write(1, buff, strlen(buff));
	
	if ((a= find_action(host[h].tag)) == -1) 		/* if no action specified, */
		return;

/* Create the temp file with the report in it. We accumulate report data in this
file until the action actually gets run. */

	sprintf(up,"/tmp/watcher.%u.%u.tmp",ident,host[h].tag);	/* our unique filename */
	if ((f= fopen(up,"a+")) == NULL) return;		/* OOPS! */
	fwrite(buff, 1, strlen(buff), f);			/* write report */
	fclose(f);

	time(&action[a].write_time);				/* time of last write */
}

/* Look through the action table, and look for actions that have reports pending.
(n) seconds after the last write, execute the action. This allows accumulating
report data for a single report, rather than a flurry of little reports. */

do_action(t, n)
time_t t;		/* current time */
int n;			/* time to wait for writes to complete */
{
char *cp,*sp;
char fn[100], buff[200];
int a, i, o;
char *args[64];		/* command line, parsed */

	o= -1;							/* log file not open yet */
	for (a= 0; a < actionn; a++) {				/* for each action */
		if (action[a].write_time == 0L) continue;	/* no report data */
		if (t - action[a].write_time < n) continue;	/* wait longer */

		action[a].write_time= 0L;			/* flag as "done" */
		sprintf(fn,"/tmp/watcher.%u.%u.tmp", ident, action[a].tag);/* our unique filename */
		if ((i= open(fn, O_RDONLY)) < 0) {		/* temp file as stdin */
			perror("watcher: action: temp file");
			unlink(fn);
			return;
		}
		if (o == -1) if ((o= open("/var/log/watcher.log", O_WRONLY | O_APPEND)) < 0) {
			perror("watcher: report: watcher.log");
			unlink(fn);
			close(i);
			return;
		}

/* Now parse and execute the complete command. */

		strcpy(buff, action[a].command);		/* make a copy */
		parse(buff, args);				/* break it up, */
		execute(args, i, o, o);				/* execute it. */

		close(i); 
		unlink(fn);					/* remove temp file */
	}
	close(o);						/* ... if open */
}

/* Locate the index of the specified action, else -1. If no action is specified,
try to locate action "!". */

find_action(a)
char a;
{
int i;

	if (a == '\0') a= '!';				/* look for set default */

	for (i= 0; i < actionn; i++)
		if (action[i].tag == a) return(i);
	return(-1);
}


/* Parse the command string, for example:
             Set ptr args[n]:   0         1  2                 3              4
				|         |  |                 |              |
				v         v  v                 v              v
				/bin/mail -s "arg with spaces" user@domain.com  

The last arg[] is a NULL ptr. parse() modifies the input string. */

parse(buff, args)
char *buff;
char **args;
{
char quote;

	quote= 0;
	while ((*buff == ' ') || (*buff == '\t') || (*buff == '\n'))
		buff++;						/* skip leading whitespace */

	while (*buff) {
		*args++= buff;					/* point to arg */

		while ((*buff != '\0') && ((*buff != ' ') || quote)  && (*buff != '\t') && (*buff != '\n')) {
			if (*buff == '"') quote = ++quote & 1;	/* toggle in-quote flag */
			buff++;					/* skip the arg */
		}
		while ((*buff == ' ') || (*buff == '\t') || (*buff == '\n'))
			*buff++= '\0';				/* trim trailing whitespace */
	}
	*args= NULL;						/* terminate w/ null ptr */
}

/* Execute the command. */

execute(args, in, out, err)
char **args;
int in, out, err;
{
int pid, status;

	if ((pid= fork()) < 0) {				/* oops! */
		perror("watcher: fork:");
		exit(1);
	}

	if (pid == 0) {						/* if child... */
		if (in != 0) { close(0); dup(in); }		/* new stdin */
		if (out != 1) { close(1); dup(out); }		/* new stdout */
		if (err != 2) { close(2); dup(out); }		/* new stderr */
		execv(*args, args);
		perror(*args);
		exit(1);
	}
	while (wait(&status) != pid);				/* ... parent */
}


/* Convert long seconds into string mm:ss, hh:mm:ss or Nd hh:mm. */

lstos(s,l)
char *s;
long l;
{
	if (l > 86400) {				/* secs per day */
		sprintf(s,"%ud ",(int) l / 86400);	/* > 1 day */
		while (*s) ++s;
		l %= 86400;
		l += 59; l %= 60;			/* drop seconds (Nd hh:mm) */
	}
	if (l > 3600) {					/* secs per hour */
		sprintf(s,"%02d:",(int) l / 3600);	/* 1 day > l > 59m 59s */
		while (*s) ++s;
		l %= 3600;
	}
	sprintf(s,"%02d:%02d",l / 60, l % 60);
}

/*
 * in_cksum --
 *	Checksum routine for Internet Protocol family headers (C Version)
 */
in_cksum(addr, len)
	u_short *addr;
	int len;
{
	register int nleft = len;
	register u_short *w = addr;
	register int sum = 0;
	u_short answer = 0;

	/*
	 * Our algorithm is simple, using a 32 bit accumulator (sum), we add
	 * sequential 16 bit words to it, and at the end, fold back all the
	 * carry bits from the top 16 bits into the lower 16 bits.
	 */
	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}

	/* mop up an odd byte, if necessary */
	if (nleft == 1) {
		*(u_char *)(&answer) = *(u_char *)w ;
		sum += answer;
	}

	/* add back carry outs from top 16 bits to low 16 bits */
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return(answer);
}

/* Generate the initial report for the idiot running this show. */

say_hello() {
char b1[40], b2[40];

	lstos(b1,(long)test_limit);
	lstos(b2,(long)test_interval);

	fprintf(stderr,"Watcher (%s) started with %d hosts, ",DATE,hostn);
	fprintf(stderr, "each tested every %s, \nmust respond within %s.", b2, b1);
	fprintf(stderr," Initial status report follows in %s.\n",b1);
}
