/* rwhod++ -- a new rwho daemon
 *
 * Oliver Laumann, net@informatik.uni-bremen.de
 *
 * $Revision: 1.14 $
 */

#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef USE_UTMP_H
#  include <utmp.h>
#endif
#include <stdarg.h>
#ifdef USE_PATH_H
#  include <paths.h>
#endif
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
#include <sys/param.h>
#include <rpc/rpc.h>
#include <rpc/clnt.h>
#include <rpc/rpc_msg.h>
#include <rpc/pmap_prot.h>
#include <rpcsvc/rusers.h>
#include <rpcsvc/rstat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <protocols/rwhod.h>


/* Functions that are missing from the system include files on some
 * platforms
 */
extern int getopt(int ac, char * const *av, const char *optstring);
#ifdef SUPPLY_GETTIMEOFDAY_PROTO
extern int gettimeofday(struct timeval *, void *);
#endif
#ifdef SUPPLY_NETGRENT_PROTO
extern void setnetgrent(const char *);
extern int getnetgrent(char **, char **, char **);
extern int endnetgrent(void);
#endif

extern char *optarg;
extern int optind, opterr;


#ifndef min
#  define min(a,b) ((a) < (b) ? (a) : (b))
#endif

#ifndef HAVE_MEMMOVE
#  define memcpy(d, s, n) bcopy ((s), (d), (n))
#  define memmove(d, s, n) bcopy ((s), (d), (n))
#endif

#ifndef MAXHOSTNAMELEN
#  define MAXHOSTNAMELEN    256
#endif

#ifndef UDPMSGSIZE
#  define UDPMSGSIZE        8800
#endif

#ifndef FSCALE
#  define FSHIFT            8
#  define FSCALE            (1<<FSHIFT)
#endif

#define INITIAL_MAX_HOSTS   32     /* initial size of host list */

#define MIN_SLEEP_TIME      500    /* min time to sleep between hosts (ms) */

#define RESET_ROUNDS_SEC    900    /* reset unresponsive host after X secs */

#define CHECK_ROUNDS_SEC    1800   /* check/re-read args each X seconds */

#ifdef _PATH_RWHODIR
#  define RWHODIR			_PATH_RWHODIR
#else									   
#  define RWHODIR             "/var/spool/rwho" 
#endif
#define FILE_PREFIX         "whod."


/* The hosts to which the RPC requests are sent must be encoded in the
 * transaction IDs, because a reply from a multi-homed may come from a
 * different IP address than the request was sent to.
 */

#define XID_REQUESTBITS     4
#define XID_REQUESTMASK     ((1 << XID_REQUESTBITS) - 1)

#define XID_REQUEST(xid)    ((xid) & XID_REQUESTMASK)
#define XID_HOST(xid)       ((xid) >> XID_REQUESTBITS)
#define MAKE_XID(req,host)  ((host) << XID_REQUESTBITS | (req))

enum request {
    REQ_PMAP_RUSERS,
    REQ_PMAP_RSTAT,
    REQ_RUSERS,
    REQ_RSTAT,
    REQ_LAST
};

char *request_names[] = {
    "portmap/rusers", "portmap/rstat", "rusers", "rstat"
};


#define HOST_RESOLVED           0x1
#define HOST_GOTPORT_RUSERS     0x2
#define HOST_GOTPORT_RSTAT      0x4
#define HOST_MARK               0x8

typedef struct {
    unsigned short flags;
    char *name;
    struct sockaddr_in sin;
    u_short rusers_port, rstat_port;
    struct whod w;
    int num_entries;           /* actual # of entries in w.wd_we */
    unsigned long errbits;     /* bit vector of error message numbers */
                               /* # of reqs since we last got a reply */
    unsigned long outstanding[REQ_LAST];
    unsigned seqnum;           /* used in XIDs */
} host;

#define ERR_SET(hostp,err)   ((hostp)->errbits |= (1 << (err)))
#define ERR_CLEAR(hostp,err) ((hostp)->errbits &= ~(1 << (err)))
#define ERR_ISSET(hostp,err) ((hostp)->errbits & (1 << (err)))

enum err {
    E_RESOLVE,
    E_DENIED,
    E_FAILED,
    E_RUSERS_NOTREG,
    E_RSTAT_NOTREG,
    E_GARBLED,
    E_TOOMANY
};


char *progname;
int debug = 0;
host *hosts;
int max_hosts = INITIAL_MAX_HOSTS;
int num_hosts;
int sock;
char **args;
int num_args;
int got_hup;
int reset_rounds;
int check_rounds;
int next_seqnum;


#define RPC_REQ_RUSERS(hostp) \
    rpcsend((hostp), (hostp)->rusers_port, REQ_RUSERS, RUSERSPROG,\
	    RUSERSVERS_IDLE, RUSERSPROC_NAMES, 0, 0);

#define RPC_REQ_RSTAT(hostp) \
    rpcsend((hostp), (hostp)->rstat_port, REQ_RSTAT, RSTATPROG,\
	    RSTATVERS_TIME, RSTATPROC_STATS, 0, 0);


void usage(void);
void add_host(char *);
void add_host_maybe(char *);
void read_hosts(char *, int, void (*)(char *));
void randomize_hosts(void);
void set_wd_hostnames(void);
void check_hosts(void);
void hup_handler(int);
void do_host(host *);
void reset_host(host *, enum request);
void rpcsend(host *, u_short, enum request, int, int, int, char *, int);
void receive_reply(void);
void update_rusers_info(host *, char *);
void update_rstat_info(host *, char *);
void write_info(host *);
void putlong(char **, long);
long getlong(char **);
unsigned long gettime_ms(void);
void hwarn(host *, enum err, char *, ...);
void warn(char *, ...);
void notice(char *, ...);
void fatal(char *, ...);
char *strerr(void);
void *safe_malloc(size_t);
void *safe_realloc(void *, size_t);


int main(int ac, char **av) {
    int t_opt = 0, a_opt = 0, r_opt = 0;
    int c, i, r, ret, flags;
    unsigned long end_time, begin_time, now;    /* ms, absolute */
    unsigned long sleep_time, time_slept;       /* ms, relative */
    long round_time_sec;
    struct timeval tv;
    fd_set rfds;
    char *s;
	char workingDir[256];
	
    if (ac == 0) {
	fprintf(stderr, "Oops--no argv[0]?\n"); exit(1);
    }
    progname = (s = strrchr(av[0], '/')) ? s+1 : av[0];

	snprintf(workingDir, 256, RWHODIR);

    while ((c = getopt(ac, av, "hdt:r:a:s:")) != EOF) {
	switch (c) {
        case 'd':
	    debug = 1; break;
	case 't':
	    if ((t_opt = atol(optarg)) <= 0) {
		fprintf(stderr, "%s: bad number of seconds: %s\n",
			progname, optarg);
		exit(1);
	    }
	    break;
	case 'r':
	    if ((r_opt = atol(optarg)) <= 0) {
		fprintf(stderr, "%s: too small argument to option -r: %s\n",
			progname, optarg);
		exit(1);
	    }
	    break;
	case 'a':
	    if ((a_opt = atol(optarg)) <= 0) {
		fprintf(stderr, "%s: too small argument to option -a: %s\n",
			progname, optarg);
		exit(1);
	    }
	    break;
	case 's':
		if (optarg && strlen(optarg) > 0) {
			strncpy(workingDir, optarg, 255);
			workingDir[255] = '\0';
		} else {
			fprintf(stderr, "%s: no argument to option -s\n", progname);
			exit(1);
		}
		break;	
	case 'h':
	case '?':
            usage();
        }
    }
    if (optind > ac-1)
	usage();
    
    if (!debug) {
	int f;

	switch (fork()) {
        case -1:
	    fprintf(stderr, "%s: fork: %s", progname, strerr()); exit(1);
	case 0:
            break;
        default:
            exit(0);
        }
	for (f = 0; f < 16; f++)
	    (void)close(f);
	(void)open("/", O_RDONLY);
	(void)dup(0);
	(void)dup(0);
#if HAVE_SETSID
	(void)setsid();
#else
	if ((f = open("/dev/tty", O_RDWR)) != -1) {
	    (void)ioctl(f, TIOCNOTTY, 0);
	    (void)close(f);
        }
#endif
    }
    openlog(progname, LOG_PID, LOG_DAEMON);

    args = av+optind;
    num_args = ac - optind;
    for (i = 0; i < num_args; i++)
	read_hosts(args[i], 1, add_host);
    randomize_hosts();
    set_wd_hostnames();
            
    if (chdir(workingDir) == -1)
	fatal("chdir: %s: %s", workingDir, strerr());

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	fatal("socket: %s", strerr());

    if ((flags = fcntl(sock, F_GETFL, 0)) == -1)
	fatal("fcntl: %s", strerr());
    flags |= O_NONBLOCK;
    if (fcntl(sock, F_SETFL, flags) == -1)
	fatal("fcntl(O_NONBLOCK): %s", strerr());

    (void)signal(SIGHUP, hup_handler);
    
    FD_ZERO(&rfds);

    end_time = gettime_ms();

    for (r = 0; ; ) {

	/* round_time_sec and (as a consequence) check_rounds and
	 * reset_rounds must be recomputed at every round, because
	 * num_hosts may change in * a call to check_hosts(): */
	round_time_sec = t_opt ? t_opt : num_hosts;

	check_rounds = a_opt ? a_opt : CHECK_ROUNDS_SEC / round_time_sec;
	if (check_rounds == 0)
	    check_rounds = 1;

	reset_rounds = r_opt ? r_opt : RESET_ROUNDS_SEC / round_time_sec;
	if (reset_rounds == 0)
	    reset_rounds = 1;
	if (debug)
		printf("reset_rounds = %d\n",reset_rounds);
	

	if (got_hup) {
	    notice("re-reading arguments because of SIGHUP");
	    got_hup = 0;
	    r = 0;       /* start over */
	    check_hosts();
	    continue;    /* ... to recompute */
	}
	
	if (r++ == check_rounds) {
	    notice("re-reading arguments after %d round(s)", check_rounds);
	    r = 0;
	    check_hosts();
	    continue;    /* ... to recompute */
	}

	end_time += round_time_sec*1000;

	for (i = 0; i < num_hosts; i++) {
	    do_host(hosts+i);

	    begin_time = gettime_ms();
	    sleep_time = end_time - begin_time;
	    if (sleep_time > (((unsigned long)~0)>>1)) {
			/* We have been too slow, adjust end_time */
			end_time = begin_time;
			sleep_time = 0;
	    }
	    sleep_time /= (num_hosts - i);
	    if (sleep_time < MIN_SLEEP_TIME)
			sleep_time = MIN_SLEEP_TIME;

	    for (;;) {
		tv.tv_sec = sleep_time / 1000;
		tv.tv_usec = sleep_time % 1000 * 1000;
		FD_SET(sock, &rfds);
		if ((ret = select(sock+1, &rfds, 0, 0, &tv)) == -1)
		    if (errno != EINTR)
			fatal("select: %s", strerr());
		if (ret == 0)
		    break;                      /* timeout: we're done */
		if (ret > 0 && FD_ISSET(sock, &rfds))
		    receive_reply();
		now = gettime_ms();
		time_slept = now - begin_time;
		if (sleep_time <= time_slept)   /* we've had enough sleep */
		    break;	                /* ...done */
		sleep_time -= time_slept;
		begin_time = now;
	    }
	}
    }
    return 0;    /* not reached; make stupid C compilers happy */
}

void usage(void) {
#ifdef PROGVERSION
	fprintf(stderr, " --- %s Version %s ---\n", progname, PROGVERSION);
#endif
    fprintf(stderr, "Usage: %s [options] file|netgroup...\n", progname);
    fprintf(stderr, "  -d           debug (don't background, don't syslog)\n");
    fprintf(stderr, "  -t seconds   time for one round through the hosts\n");
    fprintf(stderr, "  -r num       reset host if dead for NUM rounds\n");
    fprintf(stderr, "  -a num       re-read arguments each NUM rounds\n");
    fprintf(stderr, "  -s dir       spool dir (instead of %s)\n", RWHODIR);
    exit(1);
}

void add_host(char *name) {
    host *p;
    
    if (num_hosts == max_hosts)
		hosts = safe_realloc(hosts, (max_hosts *= 2) * sizeof(host));
    p = hosts + num_hosts++;
    p->flags = 0;
    p->name = safe_malloc(strlen(name)+1);
    strcpy(p->name, name);
    memset(p->outstanding, 0, sizeof p->outstanding);
    p->errbits = 0;
    p->seqnum = next_seqnum++;
        
    memset((char *)&p->w, 0, sizeof p->w);
    p->w.wd_vers = WHODVERSION;
    p->w.wd_type = WHODTYPE_STATUS;
    p->w.wd_sendtime = p->w.wd_recvtime = p->w.wd_boottime = time(0);
}

void read_hosts(char *from, int first_time, void (*add_func)(char *)) {
	if (!hosts)
		hosts = (host *)safe_malloc(max_hosts * sizeof(host));

	/* If 'from' beginns with an "/" then it's considered as an absolute path
	 * to a file with hostnames. Otherwise we look into a netgroup with 
	 * that name.
	 */
	if (from[0] == '/') {
		char buf[MAXHOSTNAMELEN+2];   /* \n \0 */
		char *p;
		FILE *f;
	
		/* A non-existing file is only considered an error on startup;
		 * it may have been removed since rwhod++ was started.
		 * (Actually, errno is not guaranteed to be valid here, but
		 * what else can we do?)
		 */
		if ((f = fopen(from, "r")) == 0) {
			if (!first_time && errno == ENOENT)
				return;
			fatal("cannot open %s", from);
		}
		while (fgets(buf, MAXHOSTNAMELEN+1, f)) {
		    if ((p = strchr(buf, '\n')) != 0)
				*p = 0;
		    add_func(buf);
		}
		(void)fclose(f);
   	} else {
		char *mach, *usr, *dom;
		int got_one = 0;

		setnetgrent(from);
		while (getnetgrent(&mach, &usr, &dom)) {
		    got_one = 1;
	    	add_func(mach);
		}
		(void)endnetgrent();
		/*
		 * An empty/non-existing netgroup is only considered an error on
		 * startup, because it may have been removed later:
		 */
		if (first_time && !got_one)
			fatal("no hostnames found in netgroup %s", from);
	}
}

void randomize_hosts(void) {
	host *p, *q;
	char *tmp;
        
	srand(time(0));
	for (p = hosts; p < hosts+num_hosts; p++) {
		q = hosts + rand() % num_hosts;
		tmp = q->name, q->name = p->name, p->name = tmp;
	}
}

void set_wd_hostnames(void) {
	host *p;
        
	for (p = hosts; p < hosts+num_hosts; p++) {
		if (strlen(p->name) > sizeof p->w.wd_hostname - 1)
			warn("hostname %s too long for struct whod (truncated)", p->name);
		strncpy(p->w.wd_hostname, p->name, sizeof p->w.wd_hostname - 1);
	}
}

/* This is called instead of add_host() when re-reading the arguments.
 * The host is only added to the list if it isn't present yet.  In any case,
 * the HOST_MARK bit is set, so that the list can be ``garbage collected''
 * at the end of check_hosts().
 */
void add_host_maybe(char *name) {
	int i;

	for (i = 0; i < num_hosts; i++)
		if (strcmp(hosts[i].name, name) == 0)
			break;
	if (i == num_hosts)
		add_host(name);
	hosts[i].flags |= HOST_MARK;
}
   
/* This function is called on receipt of a HUP signal and once every
 * check_rounds rounds.  Re-read the arguments, adding new hosts and
 * deleting hosts that are no longer present in the files or
 * netgroups.  (Newly added hosts are not randomized.)
 */
void check_hosts(void) {
	int i;
    
	(void)signal(SIGHUP, SIG_IGN);    /* check_hosts() is not reentrant */
    
	for (i = 0; i < num_args; i++)
		read_hosts(args[i], 0, add_host_maybe);

	/* Remove all hosts for which the HOST_MARK bit isn't set: */
	for (i = 0; i < num_hosts; ) {
		if (hosts[i].flags & HOST_MARK) {
			hosts[i].flags &= ~HOST_MARK;
			i++;
		} else {
			num_hosts--;
			free(hosts[i].name);
			memmove(hosts+i, hosts+i+1, (num_hosts-i) * sizeof(host));
		}
	}
	if (num_hosts == 0)
		fatal("no more hosts -- exiting");
	set_wd_hostnames();
	signal(SIGHUP, hup_handler);
}

void hup_handler(int sig) {
	signal(SIGHUP, hup_handler);
	got_hup = 1;
}

void do_host(host *p) {
	char args[4*sizeof(long)], *q;
    
	if ((p->flags & HOST_RESOLVED) == 0) {
		struct hostent *hp;
	
		if ((hp = gethostbyname(p->name)) == 0) {
			hwarn(p, E_RESOLVE, "can't resolve hostname %s", p->name);
			return;
		}
		ERR_CLEAR(p, E_RESOLVE);
		p->sin.sin_family = hp->h_addrtype;
		p->sin.sin_addr = *(struct in_addr *)hp->h_addr;
		p->flags |= HOST_RESOLVED;
	}
	if (p->flags & HOST_GOTPORT_RUSERS) {
		RPC_REQ_RUSERS(p);
	} else {
		q = args;
		putlong(&q, RUSERSPROG);
		putlong(&q, 2);
		putlong(&q, IPPROTO_UDP);
		putlong(&q, 0);
		rpcsend(p, PMAPPORT, REQ_PMAP_RUSERS, PMAPPROG, PMAPVERS,
				PMAPPROC_GETPORT, args, sizeof args);
	}
	if (p->flags & HOST_GOTPORT_RSTAT) {
		RPC_REQ_RSTAT(p);
	} else {
		q = args;
		putlong(&q, RSTATPROG);
		putlong(&q, 2);
		putlong(&q, IPPROTO_UDP);
		putlong(&q, 0);
		rpcsend(p, PMAPPORT, REQ_PMAP_RSTAT, PMAPPROG, PMAPVERS,
				PMAPPROC_GETPORT, args, sizeof args);
    }
}

void reset_host(host *hp, enum request req) {
	warn("resetting %s after %d round(s) (%s failed)", hp->name, reset_rounds,
		 request_names[req]);
	memset(hp->outstanding, 0, sizeof hp->outstanding);
	hp->errbits = 0;
	hp->flags = 0;
}

void rpcsend(host *hp, u_short port, enum request req, int prog, int vers,
			 int proc, char *args, int len) {
	char msg[UDPMSGSIZE], *p = msg;
    
	if (hp->outstanding[req]++ >= reset_rounds) {
		reset_host(hp, req);
		return;
	}

	/* We can't use the host's array index for the XID, because the array
	 * may shrink in check_hosts().
	 */
	putlong(&p, MAKE_XID(req, hp->seqnum));
	putlong(&p, CALL);
	putlong(&p, RPC_MSG_VERSION);
	putlong(&p, prog);
	putlong(&p, vers);
	putlong(&p, proc);
	putlong(&p, AUTH_NULL); putlong(&p, 0);
	putlong(&p, AUTH_NULL); putlong(&p, 0);

	memcpy(p, args, len);
	p += len;

	hp->sin.sin_port = htons(port);
	if (sendto(sock, msg, p-msg, 0, (struct sockaddr *)&hp->sin,
			   sizeof(struct sockaddr_in)) == -1)
		warn("sendto %s: %s", hp->name, strerr());
}
    
void receive_reply(void) {
	char msg[UDPMSGSIZE], *p = msg;
	struct sockaddr_in from;
	int fromlen = sizeof from, len;
	long xid, v;
	enum request request;
	unsigned seqnum;
	host *hp;
                                
	if ((len = recvfrom(sock, msg, sizeof msg, 0, (struct sockaddr *)&from,
						(socklen_t*) &fromlen)) == -1) {
		if (errno != EINTR)
			warn("recvfrom: %s", strerr());
		return;
	}

	if (len < 4 * sizeof(long)) {
		warn("RPC: short message from %s", inet_ntoa(from.sin_addr));
		return;
	}
            
	xid = getlong(&p);
	request = XID_REQUEST(xid);
	seqnum = XID_HOST(xid);
	for (hp = hosts; hp < hosts+num_hosts && hp->seqnum != seqnum; hp++);
	if (hp == hosts+num_hosts) {
		warn("RPC: bogus XID (host-ID %ld) from %s", seqnum,
			 inet_ntoa(from.sin_addr));
		return;
	}
    
	if ((v = getlong(&p)) != REPLY) {
		warn("RPC: bogus msg type %ld received", v); return;
	}
	if (getlong(&p) == MSG_DENIED) {
		switch (v = getlong(&p)) {
			case RPC_MISMATCH:
				hwarn(hp, E_DENIED, "%s: RPC: versions not compatible", hp->name);
				break;
			case AUTH_ERROR:
				hwarn(hp, E_DENIED, "%s: RPC: authentication failed", hp->name);
				break;
			default:
				hwarn(hp, E_DENIED, "%s: RPC: denied (reject_stat %ld)", hp->name, v);
				break;
		}
		return;
	}
	ERR_CLEAR(hp, E_DENIED);
    
	(void)getlong(&p); p += getlong(&p);    /* authentication */
	switch (v = getlong(&p)) {
		case SUCCESS:
			break;
		case PROG_UNAVAIL:
			hwarn(hp, E_FAILED, "%s: RPC: program unavailable", hp->name);
			return;
		case PROG_MISMATCH:
			hwarn(hp, E_FAILED, "%s: RPC: program/version mismatch", hp->name);
			return;
		case PROC_UNAVAIL:
			hwarn(hp, E_FAILED, "%s: RPC: procedure unavailable", hp->name);
			return;
		case GARBAGE_ARGS:
			hwarn(hp, E_FAILED, "%s: RPC: can't decode arguments", hp->name);
			return;
		default:
			hwarn(hp, E_FAILED, "%s: RPC: failed (reason %ld)", hp->name, v);
			return;
	}
	ERR_CLEAR(hp, E_FAILED);

	/* here we decode the answer we got.
	 */
	switch (request) {
		case REQ_PMAP_RUSERS:
			/* The answer was from portmapper, about the rusersd portnumber */
			if ((hp->rusers_port = getlong(&p)) == 0) {
				hwarn(hp, E_RUSERS_NOTREG, 
					  "%s: RPC: program not registered (rusers)", hp->name);
				return;
			}
			ERR_CLEAR(hp, E_RUSERS_NOTREG);
			hp->flags |= HOST_GOTPORT_RUSERS;
			hp->outstanding[request] = 0;
			RPC_REQ_RUSERS(hp);
			break;
		case REQ_PMAP_RSTAT:
			/* The answer was from portmapper, about the rstatd portnumber */
			if ((hp->rstat_port = getlong(&p)) == 0) {
				hwarn(hp, E_RSTAT_NOTREG, 
					  "%s: RPC: program not registered (rstat)", hp->name);
				return;
			}
			ERR_CLEAR(hp, E_RSTAT_NOTREG);
			hp->flags |= HOST_GOTPORT_RSTAT;
			hp->outstanding[request] = 0;
			RPC_REQ_RSTAT(hp);
			break;
		case REQ_RUSERS: 
			/* Answer from rusersd */
			hp->outstanding[request] = 0;
			update_rusers_info(hp, p);
			write_info(hp);
			break;
		case REQ_RSTAT:
			/* Answer from rstatd */
			hp->outstanding[request] = 0;
			update_rstat_info(hp, p);
			write_info(hp);
			break;
		default:
			warn("%s: RPC: bogus request %u in XID", hp->name, request);
	}
}

void update_rusers_info(host *hp, char *p) {
	int i, n, len;
	const int max = sizeof hp->w.wd_we / sizeof(struct whoent);
                        
	n = getlong(&p);
	if (n < 0) {
		hwarn(hp, E_GARBLED, "garbled rusers reply from %s (%d utmp entries)",
			  hp->name, n);
		return;
	}
	ERR_CLEAR(hp, E_GARBLED);
	if (n > max) {
		hwarn(hp, E_TOOMANY,
			  "too many utmp entries (%d) from %s, discarding some",
			  n, hp->name);
		n = max;
	} else
		ERR_CLEAR(hp, E_TOOMANY);
    
	for (i = 0; i < n; i++) {
		struct outmp *up = &hp->w.wd_we[i].we_utmp;
	
		/* rwho seems to require that out_line be \0-terminated:
		 */
		len = getlong(&p);
		strncpy(up->out_line, p, min(len, sizeof up->out_line - 1));
		up->out_line[sizeof up->out_line - 1] = 0;
		p += len;

		len = getlong(&p);
		strncpy(up->out_name, p, min(len, sizeof up->out_name));
		p += len;

		len = getlong(&p);               /* ut_host */
		p += len;

		up->out_time = getlong(&p);
		hp->w.wd_we[i].we_idle = getlong(&p) * 60;
	}
	hp->num_entries = n;

	hp->w.wd_recvtime = time(0);
}

void update_rstat_info(host *hp, char *p) {
	int i;
    
	p += 18 * sizeof(long);    /* first part of struct statstime */

	for (i = 0; i < 3; i++)
		hp->w.wd_loadav[i] = (double)getlong(&p)/FSCALE * 100;

	hp->w.wd_boottime = getlong(&p);    /* tv_sec */
	(void)getlong(&p);                  /* tv_usec */
	hp->w.wd_sendtime = getlong(&p);    /* tv_sec */

	hp->w.wd_recvtime = time(0);
}

void write_info(host *hp) {
	int fd;
	char fn[MAXHOSTNAMELEN + sizeof(FILE_PREFIX) + 1];
	struct stat st;
	const int len = sizeof(struct whod) -
					sizeof hp->w.wd_we + hp->num_entries * sizeof(struct whoent);

	(void)snprintf(fn, MAXHOSTNAMELEN + sizeof(FILE_PREFIX), "%s%s", FILE_PREFIX, hp->name);
	if ((fd = open(fn, O_WRONLY|O_CREAT, 0666)) == -1) {
		warn("%s: %s", fn, strerr());
		return;
	}
	if (write(fd, (char *)&hp->w, len) == -1)
		warn("%s: %s", fn, strerr);
	if (fstat(fd, &st) == -1 || st.st_size > len)
		(void)ftruncate(fd, len);
	(void)close(fd);
}

void putlong(char **p, long l) {
	*(long *)*p = (long)htonl((u_long)l);
	*p += sizeof(long);
}

long getlong(char **p) {
	long l = (long)ntohl((u_long)(*(long *)*p));
	*p += sizeof(long);
	return l;
}

unsigned long gettime_ms(void) {
	struct timeval t;
    
	(void)gettimeofday(&t, 0);
	return t.tv_sec*1000 + t.tv_usec/1000;
}

#ifndef HAVE_VSYSLOG
void vsyslog(int pri, const char *fmt, va_list args) {
	char buf[1024];		/* artificial limit; can't avoid this */
    
	(void)vsnprintf(buf, 1024, fmt, args);
	buf[1023] = 0;		/* just to be sure */
	syslog(pri, buf);
}
#endif

void hwarn(host *hp, enum err err, char *fmt, ...) {
	va_list args;
    
	if (ERR_ISSET(hp, err))
		return;
	ERR_SET(hp, err);

	va_start(args, fmt);
	if (debug) {
		fprintf(stderr, "%s: warning: ", progname);
		vfprintf(stderr, fmt, args);
		fprintf(stderr, ".\n");
	} else
		vsyslog(LOG_WARNING, fmt, args);
}

void warn(char *fmt, ...) {
	va_list args;

	va_start(args, fmt);
	if (debug) {
		fprintf(stderr, "%s: warning: ", progname);
		vfprintf(stderr, fmt, args);
		fprintf(stderr, ".\n");
	} else
		vsyslog(LOG_WARNING, fmt, args);
}

void notice(char *fmt, ...) {
	va_list args;

	va_start(args, fmt);
	if (debug) {
		fprintf(stderr, "%s: ", progname);
		vfprintf(stderr, fmt, args);
		fprintf(stderr, ".\n");
	} else
		vsyslog(LOG_NOTICE, fmt, args);
}

void fatal(char *fmt, ...) {
	va_list args;

	va_start(args, fmt);
	if (debug) {
		fprintf(stderr, "%s: ", progname);
		vfprintf(stderr, fmt, args);
		fprintf(stderr, ".\n");
	} else
		vsyslog(LOG_ERR, fmt, args);
	exit(1);
}

char *strerr(void) {
    return errno > 0 ?
	strerror(errno) : "unknown error";
}

void *safe_malloc(size_t n) {
	void *p;

	if ((p = malloc(n)) == 0)
		fatal("cannot malloc %lu bytes--virtual memory exhausted",
			  (unsigned long)n);
	return p;
}
	
void *safe_realloc(void *old, size_t n) {
	void *p;

	if ((p = realloc(old, n)) == 0)
		fatal("cannot realloc %lu bytes--virtual memory exhausted",
			  (unsigned long)n);
	return p;
}
