/* 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>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.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 */

#define RWHODIR             "/usr/spool/rwho"
#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;
                            
    if (ac == 0) {
	fprintf(stderr, "Oops--no argv[0]?\n"); exit(1);
    }
    progname = (s = strrchr(av[0], '/')) ? s+1 : av[0];

    while ((c = getopt(ac, av, "dt:r:a:")) != 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 '?':
            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(RWHODIR) == -1)
	fatal("chdir: %s: %s", RWHODIR, 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;
	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) {
    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");
    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 = htonl(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[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, htons(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, htons(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 = 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,
			&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);

    switch (request) {
    case REQ_PMAP_RUSERS:
	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:
	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: 
	hp->outstanding[request] = 0;
	update_rusers_info(hp, p);
	write_info(hp);
    	break;
    case REQ_RSTAT:
	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 = htonl(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 = htonl(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)sprintf(fn, "%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)vsprintf(buf, fmt, args);
    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) {
    extern int sys_nerr;
    extern char *sys_errlist[];

    return errno > 0 && errno < sys_nerr ?
	sys_errlist[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;
}
