xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/in.rwhod.c (revision 2a6e99a0f1f7d22c0396e8b2ce9b9babbd1056cf)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * Portions of this source code were derived from Berkeley 4.3 BSD
32  * under license from the Regents of the University of California.
33  */
34 
35 #pragma ident	"%Z%%M%	%I%	%E% SMI"
36 
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/socket.h>
40 #include <sys/sockio.h>
41 #include <sys/stat.h>
42 #include <sys/ioctl.h>
43 #include <sys/file.h>
44 #include <sys/loadavg.h>
45 
46 #include <net/if.h>
47 #include <netinet/in.h>
48 
49 #include <stdio.h>
50 #include <signal.h>
51 #include <errno.h>
52 #include <utmpx.h>
53 #include <ctype.h>
54 #include <netdb.h>
55 #include <syslog.h>
56 #include <fcntl.h>
57 #include <sys/isa_defs.h>	/* for ENDIAN defines */
58 #include <arpa/inet.h>
59 #include <protocols/rwhod.h>
60 
61 #include <strings.h>
62 #include <stdlib.h>
63 #include <unistd.h>
64 
65 /*
66  * This version of Berkeley's rwhod has been modified to use IP multicast
67  * datagrams, under control of a new command-line option:
68  *
69  *	rwhod -m	causes rwhod to use IP multicast (instead of
70  *			broadcast or unicast) on all interfaces that have
71  *			the IFF_MULTICAST flag set in their "ifnet" structs
72  *			(excluding the loopback interface).  The multicast
73  *			reports are sent with a time-to-live of 1, to prevent
74  *			forwarding beyond the directly-connected subnet(s).
75  *
76  *	rwhod -m <ttl>	causes rwhod to send IP multicast datagrams with a
77  *			time-to-live of <ttl>, via a SINGLE interface rather
78  *			than all interfaces.  <ttl> must be between 0 and
79  *			MAX_MULTICAST_SCOPE, defined below.  Note that "-m 1"
80  *			is different than "-m", in that "-m 1" specifies
81  *			transmission on one interface only.
82  *
83  * When "-m" is used without a <ttl> argument, the program accepts multicast
84  * rwhod reports from all multicast-capable interfaces.  If a <ttl> argument
85  * is given, it accepts multicast reports from only one interface, the one
86  * on which reports are sent (which may be controlled via the host's routing
87  * table).  Regardless of the "-m" option, the program accepts broadcast or
88  * unicast reports from all interfaces.  Thus, this program will hear the
89  * reports of old, non-multicasting rwhods, but, if multicasting is used,
90  * those old rwhods won't hear the reports generated by this program.
91  *
92  *                  -- Steve Deering, Stanford University, February 1989
93  */
94 
95 #define	NO_MULTICAST		0	  /* multicast modes */
96 #define	PER_INTERFACE_MULTICAST	1
97 #define	SCOPED_MULTICAST	2
98 
99 #define	MAX_MULTICAST_SCOPE	32	  /* "site-wide", by convention */
100 
101 #define	INADDR_WHOD_GROUP	(ulong_t)0xe0000103	/* 224.0.1.3 */
102 					/* (belongs in protocols/rwhod.h) */
103 
104 static int			multicast_mode  = NO_MULTICAST;
105 static int			multicast_scope;
106 static struct sockaddr_in	multicast_addr  = { AF_INET };
107 
108 
109 /*
110  * Alarm interval. Don't forget to change the down time check in ruptime
111  * if this is changed.
112  */
113 #define	AL_INTERVAL (3 * 60)
114 
115 static struct	sockaddr_in sin = { AF_INET };
116 
117 static char	myname[MAXHOSTNAMELEN];
118 
119 /*
120  * We communicate with each neighbor in
121  * a list constructed at the time we're
122  * started up.  Neighbors are currently
123  * directly connected via a hardware interface.
124  */
125 struct	neighbor {
126 	struct	neighbor *n_next;
127 	char	*n_name;		/* interface name */
128 	char	*n_addr;		/* who to send to */
129 	int	n_addrlen;		/* size of address */
130 	ulong_t	n_subnet;		/* AF_INET subnet */
131 	uint_t	n_flags;		/* should forward?, interface flags */
132 };
133 
134 static struct	neighbor *neighbors;
135 static struct	whod mywd;
136 static struct	servent *sp;
137 static int	s;
138 
139 #define	WHDRSIZE	(sizeof (mywd) - sizeof (mywd.wd_we))
140 #define	RWHODIR		"/var/spool/rwho"
141 
142 static void		onalrm(void);
143 static void		getkmem(void);
144 static boolean_t	configure(int);
145 static int		verify(const struct whod *);
146 
147 int
148 main(int argc, char *argv[])
149 {
150 	struct sockaddr_in from;
151 	struct stat st;
152 	char path[64];
153 	struct hostent *hp;
154 	int on = 1;
155 	char *cp;
156 	struct stat sb;
157 
158 	if (getuid()) {
159 		(void) fprintf(stderr, "in.rwhod: not super user\n");
160 		exit(1);
161 	}
162 	sp = getservbyname("who", "udp");
163 	if (sp == NULL) {
164 		(void) fprintf(stderr, "in.rwhod: udp/who: unknown service\n");
165 		exit(1);
166 	}
167 	argv++;
168 	argc--;
169 	while (argc > 0 && *argv[0] == '-') {
170 		if (strcmp(*argv, "-m") == 0) {
171 			if (argc > 1 && isdigit(*(argv + 1)[0])) {
172 				argv++;
173 				argc--;
174 				multicast_mode  = SCOPED_MULTICAST;
175 				multicast_scope = atoi(*argv);
176 				if (multicast_scope > MAX_MULTICAST_SCOPE) {
177 					(void) fprintf(stderr,
178 					    "in.rwhod: "
179 					    "ttl must not exceed %u\n",
180 					    MAX_MULTICAST_SCOPE);
181 					exit(1);
182 				}
183 			} else {
184 				multicast_mode = PER_INTERFACE_MULTICAST;
185 			}
186 		} else {
187 			goto usage;
188 		}
189 		argv++;
190 		argc--;
191 	}
192 	if (argc > 0)
193 		goto usage;
194 	if (chdir(RWHODIR) < 0) {
195 		perror(RWHODIR);
196 		exit(1);
197 	}
198 #ifndef DEBUG
199 	if (fork())
200 		exit(0);
201 	/* CSTYLED */
202 	{
203 		(void) close(0);
204 		(void) close(1);
205 		(void) close(2);
206 		(void) open("/", 0);
207 		(void) dup2(0, 1);
208 		(void) dup2(0, 2);
209 		(void) setsid();
210 	}
211 #endif
212 	(void) sigset(SIGHUP, (void (*)())getkmem);
213 	openlog("in.rwhod", LOG_PID, LOG_DAEMON);
214 	/*
215 	 * Establish host name as returned by system.
216 	 */
217 	if (gethostname(myname, sizeof (myname) - 1) < 0) {
218 		syslog(LOG_ERR, "main: gethostname: %m");
219 		exit(1);
220 	}
221 	if ((cp = index(myname, '.')) != NULL)
222 		*cp = '\0';
223 	(void) strlcpy(mywd.wd_hostname, myname, sizeof (mywd.wd_hostname));
224 
225 	if (stat(UTMPX_FILE, &sb) < 0) {
226 		syslog(LOG_ERR, "main: stat: %s: %m", UTMPX_FILE);
227 		exit(1);
228 	}
229 	getkmem();
230 	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
231 		syslog(LOG_ERR, "main: socket: %m");
232 		exit(1);
233 	}
234 	if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof (on)) < 0) {
235 		syslog(LOG_ERR, "main: setsockopt SO_BROADCAST: %m");
236 		exit(1);
237 	}
238 	hp = gethostbyname(myname);
239 	if (hp == NULL) {
240 		syslog(LOG_ERR, "main: %s: don't know my own name\n", myname);
241 		exit(1);
242 	}
243 	sin.sin_family = hp->h_addrtype;
244 	sin.sin_port = sp->s_port;
245 	if (bind(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
246 		syslog(LOG_ERR, "main: bind: %m");
247 		exit(1);
248 	}
249 	if (!configure(s))
250 		exit(1);
251 	(void) sigset(SIGALRM, (void (*)())onalrm);
252 	onalrm();
253 	for (;;) {
254 		struct whod wd;
255 		int cc, whod;
256 		socklen_t len = sizeof (from);
257 
258 		cc = recvfrom(s, &wd, sizeof (struct whod), 0,
259 		    (struct sockaddr *)&from, &len);
260 		if (cc <= 0) {
261 			if (cc < 0 && errno != EINTR)
262 				syslog(LOG_WARNING, "main: recvfrom: %m");
263 			continue;
264 		}
265 		if (from.sin_port != sp->s_port) {
266 			syslog(LOG_WARNING, "main: %d: bad from port",
267 			    ntohs(from.sin_port));
268 			continue;
269 		}
270 #ifdef notdef
271 		if (gethostbyname(wd.wd_hostname) == 0) {
272 			syslog(LOG_WARNING, "main: %s: unknown host",
273 			    wd.wd_hostname);
274 			continue;
275 		}
276 #endif
277 		if (wd.wd_vers != WHODVERSION)
278 			continue;
279 		if (wd.wd_type != WHODTYPE_STATUS)
280 			continue;
281 		if (!verify(&wd)) {
282 			syslog(LOG_WARNING, "main: malformed host name from %x",
283 			    from.sin_addr.s_addr);
284 			continue;
285 		}
286 		(void) sprintf(path, "whod.%s", wd.wd_hostname);
287 		/*
288 		 * Rather than truncating and growing the file each time,
289 		 * use ftruncate if size is less than previous size.
290 		 */
291 		whod = open(path, O_WRONLY | O_CREAT, 0644);
292 		if (whod < 0) {
293 			syslog(LOG_WARNING, "main: open: %s: %m", path);
294 			continue;
295 		}
296 #if defined(_LITTLE_ENDIAN)
297 		/* CSTYLED */
298 		{
299 			int i, n = (cc - WHDRSIZE)/sizeof (struct whoent);
300 			struct whoent *we;
301 
302 			/* undo header byte swapping before writing to file */
303 			wd.wd_sendtime = ntohl(wd.wd_sendtime);
304 			for (i = 0; i < 3; i++)
305 				wd.wd_loadav[i] = ntohl(wd.wd_loadav[i]);
306 			wd.wd_boottime = ntohl(wd.wd_boottime);
307 			we = wd.wd_we;
308 			for (i = 0; i < n; i++) {
309 				we->we_idle = ntohl(we->we_idle);
310 				we->we_utmp.out_time =
311 				    ntohl(we->we_utmp.out_time);
312 				we++;
313 			}
314 		}
315 #endif
316 		(void) time((time_t *)&wd.wd_recvtime);
317 		(void) write(whod, &wd, cc);
318 		if (fstat(whod, &st) < 0 || st.st_size > cc)
319 			(void) ftruncate(whod, cc);
320 		(void) close(whod);
321 	}
322 	/* NOTREACHED */
323 usage:
324 	(void) fprintf(stderr, "usage: in.rwhod [ -m [ ttl ] ]\n");
325 	return (1);
326 }
327 
328 /*
329  * Check out host name for unprintables
330  * and other funnies before allowing a file
331  * to be created.  Sorry, but blanks aren't allowed.
332  */
333 static int
334 verify(const struct whod *wd)
335 {
336 	int size = 0;
337 	const char *name = wd->wd_hostname;
338 
339 	/*
340 	 * We shouldn't assume the name is NUL terminated, so bound the
341 	 * checks at the size of the whod structures wd_hostname field.
342 	 */
343 	while ((size < sizeof (wd->wd_hostname)) &&
344 	    (*name != '\0')) {
345 		if (*name == '/' || !isascii(*name) ||
346 		    !(isalnum(*name) || ispunct(*name)))
347 			return (0);
348 		name++, size++;
349 	}
350 	/*
351 	 * Fail the verification if NULL name or it wasn't NUL terminated.
352 	 */
353 	return ((size > 0) && (size < sizeof (wd->wd_hostname)));
354 }
355 
356 static int	utmpxtime;
357 static int	utmpxent;
358 static int	alarmcount;
359 struct	utmpx *utmpx;
360 
361 static void
362 onalrm(void)
363 {
364 	int i;
365 	struct stat stb;
366 	int	utmpxsize = 0;
367 	int	entries;
368 	struct	utmpx *utp;
369 	struct	utmpx *utmpxbegin;
370 	struct whoent *we = mywd.wd_we, *wlast;
371 	int cc, cnt;
372 	double avenrun[3];
373 
374 	time_t now = time(0);
375 	struct neighbor *np;
376 
377 	if (alarmcount % 10 == 0)
378 		getkmem();
379 	alarmcount++;
380 	(void) stat(UTMPX_FILE, &stb);
381 	entries = stb.st_size / sizeof (struct futmpx);
382 	if ((stb.st_mtime != utmpxtime) || (entries > utmpxent)) {
383 		utmpxtime = stb.st_mtime;
384 		if (entries > utmpxent) {
385 			utmpxent = entries;
386 			utmpxsize = utmpxent * sizeof (struct utmpx);
387 			utmpx = realloc(utmpx, utmpxsize);
388 			if (utmpx == NULL) {
389 				syslog(LOG_ERR, "onalrm: realloc: %m");
390 				utmpxsize = 0;
391 				goto done;
392 			}
393 		}
394 		utmpxbegin = utmpx;
395 		setutxent();
396 		cnt = 0;
397 		while (cnt++ < utmpxent && (utp = getutxent()) != NULL)
398 			(void) memcpy(utmpxbegin++, utp, sizeof (struct utmpx));
399 		endutxent();
400 		wlast = &mywd.wd_we[1024 / sizeof (struct whoent) - 1];
401 		for (i = 0; i < utmpxent; i++) {
402 			if (utmpx[i].ut_name[0] &&
403 			    utmpx[i].ut_type == USER_PROCESS) {
404 				/*
405 				 * XXX - utmpx name and line lengths should
406 				 * be here
407 				 */
408 				bcopy(utmpx[i].ut_line, we->we_utmp.out_line,
409 				    sizeof (we->we_utmp.out_line));
410 				bcopy(utmpx[i].ut_name, we->we_utmp.out_name,
411 				    sizeof (we->we_utmp.out_name));
412 				we->we_utmp.out_time =
413 				    htonl(utmpx[i].ut_xtime);
414 				if (we >= wlast)
415 					break;
416 				we++;
417 			}
418 		}
419 		utmpxent = we - mywd.wd_we;
420 	}
421 
422 	/*
423 	 * The test on utmpxent looks silly---after all, if no one is
424 	 * logged on, why worry about efficiency?---but is useful on
425 	 * (e.g.) compute servers.
426 	 */
427 	if (utmpxent > 0 && chdir("/dev") == -1) {
428 		syslog(LOG_ERR, "onalrm: chdir /dev: %m");
429 		exit(1);
430 	}
431 	we = mywd.wd_we;
432 	for (i = 0; i < utmpxent; i++) {
433 		if (stat(we->we_utmp.out_line, &stb) >= 0)
434 			we->we_idle = htonl(now - stb.st_atime);
435 		we++;
436 	}
437 	if (getloadavg(avenrun, 3) == -1) {
438 		syslog(LOG_ERR, "onalrm: getloadavg: %m");
439 		exit(1);
440 	}
441 
442 	for (i = 0; i < 3; i++)
443 		mywd.wd_loadav[i] = htonl((ulong_t)(avenrun[i] * 100));
444 	cc = (char *)we - (char *)&mywd;
445 	mywd.wd_sendtime = htonl(time(0));
446 	mywd.wd_vers = WHODVERSION;
447 	mywd.wd_type = WHODTYPE_STATUS;
448 	if (multicast_mode == SCOPED_MULTICAST) {
449 		(void) sendto(s, &mywd, cc, 0,
450 		    (struct sockaddr *)&multicast_addr,
451 		    sizeof (multicast_addr));
452 	} else for (np = neighbors; np != NULL; np = np->n_next) {
453 		if (multicast_mode == PER_INTERFACE_MULTICAST &&
454 		    np->n_flags & IFF_MULTICAST) {
455 			/*
456 			 * Select the outgoing interface for the multicast.
457 			 */
458 			if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF,
459 			    &(((struct sockaddr_in *)np->n_addr)->sin_addr),
460 			    sizeof (struct in_addr)) < 0) {
461 				syslog(LOG_ERR,
462 				    "onalrm: setsockopt IP_MULTICAST_IF: %m");
463 				exit(1);
464 			}
465 			(void) sendto(s, &mywd, cc, 0,
466 			    (struct sockaddr *)&multicast_addr,
467 			    sizeof (multicast_addr));
468 		} else {
469 			(void) sendto(s, &mywd, cc, 0,
470 			    (struct sockaddr *)np->n_addr, np->n_addrlen);
471 		}
472 	}
473 	if (utmpxent > 0 && chdir(RWHODIR) == -1) {
474 		syslog(LOG_ERR, "onalrm: chdir %s: %m", RWHODIR);
475 		exit(1);
476 	}
477 done:
478 	(void) alarm(AL_INTERVAL);
479 }
480 
481 static void
482 getkmem(void)
483 {
484 	struct utmpx *utmpx, utmpx_id;
485 
486 	utmpx_id.ut_type = BOOT_TIME;
487 	if ((utmpx = getutxid(&utmpx_id)) != NULL)
488 		mywd.wd_boottime = utmpx->ut_xtime;
489 	endutxent();
490 	mywd.wd_boottime = htonl(mywd.wd_boottime);
491 }
492 
493 /*
494  * Figure out device configuration and select
495  * networks which deserve status information.
496  */
497 static boolean_t
498 configure(int s)
499 {
500 	char *buf;
501 	struct ifconf ifc;
502 	struct ifreq ifreq, *ifr;
503 	struct sockaddr_in *sin;
504 	struct neighbor *np;
505 	struct neighbor *np2;
506 	int n;
507 	int numifs;
508 	unsigned bufsize;
509 
510 	if (multicast_mode == SCOPED_MULTICAST) {
511 		struct ip_mreq mreq;
512 		unsigned char ttl;
513 
514 		mreq.imr_multiaddr.s_addr = htonl(INADDR_WHOD_GROUP);
515 		mreq.imr_interface.s_addr = htonl(INADDR_ANY);
516 		if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
517 		    sizeof (mreq)) < 0) {
518 			syslog(LOG_ERR,
519 			    "configure: setsockopt IP_ADD_MEMBERSHIP: %m");
520 			return (B_FALSE);
521 		}
522 		ttl = multicast_scope;
523 		if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
524 		    sizeof (ttl)) < 0) {
525 			syslog(LOG_ERR,
526 			    "configure: setsockopt IP_MULTICAST_TTL: %m");
527 			return (B_FALSE);
528 		}
529 		multicast_addr.sin_addr.s_addr = htonl(INADDR_WHOD_GROUP);
530 		multicast_addr.sin_port = sp->s_port;
531 		return (B_TRUE);
532 	}
533 
534 	if (ioctl(s, SIOCGIFNUM, (char *)&numifs) < 0) {
535 		syslog(LOG_ERR, "configure: ioctl SIOCGIFNUM: %m");
536 		return (B_FALSE);
537 	}
538 	bufsize = numifs * sizeof (struct ifreq);
539 	buf = malloc(bufsize);
540 	if (buf == NULL) {
541 		syslog(LOG_ERR, "configure: malloc: %m");
542 		return (B_FALSE);
543 	}
544 	ifc.ifc_len = bufsize;
545 	ifc.ifc_buf = buf;
546 	if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0) {
547 		syslog(LOG_ERR,
548 		    "configure: ioctl (get interface configuration): %m");
549 		(void) free(buf);
550 		return (B_FALSE);
551 	}
552 	ifr = ifc.ifc_req;
553 	for (n = ifc.ifc_len / sizeof (struct ifreq); n > 0; n--, ifr++) {
554 		/* Skip all logical interfaces */
555 		if (index(ifr->ifr_name, ':') != NULL)
556 			continue;
557 
558 		for (np = neighbors; np != NULL; np = np->n_next) {
559 			if (np->n_name &&
560 			    strcmp(ifr->ifr_name, np->n_name) == 0)
561 				break;
562 		}
563 		if (np != NULL)
564 			continue;
565 		ifreq = *ifr;
566 		np = (struct neighbor *)malloc(sizeof (*np));
567 		if (np == NULL)
568 			continue;
569 		np->n_name = malloc(strlen(ifr->ifr_name) + 1);
570 		if (np->n_name == NULL) {
571 			free(np);
572 			continue;
573 		}
574 		(void) strcpy(np->n_name, ifr->ifr_name);
575 		np->n_addrlen = sizeof (ifr->ifr_addr);
576 		np->n_addr = malloc(np->n_addrlen);
577 		if (np->n_addr == NULL) {
578 			free(np->n_name);
579 			free(np);
580 			continue;
581 		}
582 		bcopy(&ifr->ifr_addr, np->n_addr, np->n_addrlen);
583 		if (ioctl(s, SIOCGIFFLAGS, (char *)&ifreq) < 0) {
584 			syslog(LOG_ERR,
585 			    "configure: ioctl (get interface flags): %m");
586 			free(np->n_addr);
587 			free(np->n_name);
588 			free(np);
589 			continue;
590 		}
591 		np->n_flags = ifreq.ifr_flags;
592 		if (((struct sockaddr_in *)np->n_addr)->sin_family == AF_INET &&
593 		    ioctl(s, SIOCGIFNETMASK, (char *)&ifreq) >= 0) {
594 			sin = (struct sockaddr_in *)np->n_addr;
595 
596 			np->n_subnet = sin->sin_addr.s_addr &
597 			    ((struct sockaddr_in *)&ifreq.ifr_addr)->
598 			    sin_addr.s_addr;
599 		}
600 		if (multicast_mode == PER_INTERFACE_MULTICAST &&
601 		    (np->n_flags & IFF_UP) &&
602 		    (np->n_flags & IFF_MULTICAST) &&
603 		    !(np->n_flags & IFF_LOOPBACK)) {
604 			struct ip_mreq mreq;
605 
606 			/*
607 			 * Skip interfaces that have matching subnets i.e.
608 			 * (addr & netmask) are identical.
609 			 * Such interfaces are connected to the same
610 			 * physical wire.
611 			 */
612 			for (np2 = neighbors; np2 != NULL; np2 = np2->n_next) {
613 
614 				if (!(np->n_flags & IFF_POINTOPOINT) &&
615 				    !(np2->n_flags & IFF_POINTOPOINT) &&
616 				    (np->n_subnet == np2->n_subnet)) {
617 					free(np->n_addr);
618 					free(np->n_name);
619 					free(np);
620 					break;
621 				}
622 			}
623 			if (np2 != NULL)
624 				continue;
625 
626 			mreq.imr_multiaddr.s_addr = htonl(INADDR_WHOD_GROUP);
627 			mreq.imr_interface.s_addr =
628 			    ((struct sockaddr_in *)np->n_addr)->sin_addr.s_addr;
629 			if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
630 			    sizeof (mreq)) < 0) {
631 				syslog(LOG_ERR,
632 				    "configure: "
633 				    "setsockopt IP_ADD_MEMBERSHIP: %m");
634 				free(np->n_addr);
635 				free(np->n_name);
636 				free(np);
637 				continue;
638 			}
639 			multicast_addr.sin_addr.s_addr =
640 			    htonl(INADDR_WHOD_GROUP);
641 			multicast_addr.sin_port = sp->s_port;
642 			np->n_next = neighbors;
643 			neighbors = np;
644 			continue;
645 		}
646 		if ((np->n_flags & IFF_UP) == 0 ||
647 		    (np->n_flags & (IFF_BROADCAST|IFF_POINTOPOINT)) == 0) {
648 			free(np->n_addr);
649 			free(np->n_name);
650 			free(np);
651 			continue;
652 		}
653 		if (np->n_flags & IFF_POINTOPOINT) {
654 			if (ioctl(s, SIOCGIFDSTADDR, (char *)&ifreq) < 0) {
655 				syslog(LOG_ERR,
656 				    "configure: ioctl (get dstaddr): %m");
657 				free(np->n_addr);
658 				free(np->n_name);
659 				free(np);
660 				continue;
661 			}
662 			/* we assume addresses are all the same size */
663 			bcopy(&ifreq.ifr_dstaddr, np->n_addr, np->n_addrlen);
664 		}
665 		if (np->n_flags & IFF_BROADCAST) {
666 			if (ioctl(s, SIOCGIFBRDADDR, (char *)&ifreq) < 0) {
667 				syslog(LOG_ERR,
668 				    "configure: ioctl (get broadaddr): %m");
669 				free(np->n_addr);
670 				free(np->n_name);
671 				free(np);
672 				continue;
673 			}
674 			/* we assume addresses are all the same size */
675 			bcopy(&ifreq.ifr_broadaddr, np->n_addr, np->n_addrlen);
676 		}
677 		/* gag, wish we could get rid of Internet dependencies */
678 		sin = (struct sockaddr_in *)np->n_addr;
679 		sin->sin_port = sp->s_port;
680 
681 		/*
682 		 * Avoid adding duplicate broadcast and pt-pt destinations
683 		 * to the list.
684 		 */
685 		for (np2 = neighbors; np2 != NULL; np2 = np2->n_next) {
686 			struct sockaddr_in *sin2;
687 
688 			sin2 = (struct sockaddr_in *)np2->n_addr;
689 			if (sin2->sin_addr.s_addr == sin->sin_addr.s_addr) {
690 				free(np->n_addr);
691 				free(np->n_name);
692 				free(np);
693 				break;
694 			}
695 		}
696 		if (np2 != NULL)
697 			continue;
698 
699 		np->n_next = neighbors;
700 		neighbors = np;
701 	}
702 	(void) free(buf);
703 	return (B_TRUE);
704 }
705 
706 #ifdef DEBUG
707 static char *interval(uint_t, char *);
708 
709 /* ARGSUSED */
710 static ssize_t
711 sendto(int s, const void *buf, size_t cc, int flags, const struct sockaddr *to,
712     socklen_t tolen)
713 {
714 	struct whod *w = (struct whod *)buf;
715 	struct whoent *we;
716 	struct sockaddr_in *sin = (struct sockaddr_in *)to;
717 	int nsz;
718 
719 	(void) printf("sendto %x.%d\n", ntohl(sin->sin_addr.s_addr),
720 	    ntohs(sin->sin_port));
721 	(void) printf("hostname %s %s\n", w->wd_hostname,
722 	    interval(ntohl(w->wd_sendtime) - ntohl(w->wd_boottime), "  up"));
723 	(void) printf("load %4.2f, %4.2f, %4.2f\n",
724 	    ntohl(w->wd_loadav[0]) / 100.0, ntohl(w->wd_loadav[1]) / 100.0,
725 	    ntohl(w->wd_loadav[2]) / 100.0);
726 	cc -= WHDRSIZE;
727 	for (we = w->wd_we, cc /= sizeof (struct whoent); cc > 0; cc--, we++) {
728 		time_t t = ntohl(we->we_utmp.out_time);
729 
730 		nsz = sizeof (we->we_utmp.out_name);
731 		(void) printf("%-*.*s %s:%s %.12s",
732 		    nsz,
733 		    nsz,
734 		    we->we_utmp.out_name,
735 		    w->wd_hostname,
736 		    we->we_utmp.out_line,
737 		    ctime(&t)+4);
738 		we->we_idle = ntohl(we->we_idle) / 60;
739 		if (we->we_idle) {
740 			if (we->we_idle >= 100*60)
741 				we->we_idle = 100*60 - 1;
742 			if (we->we_idle >= 60)
743 				(void) printf(" %2d", we->we_idle / 60);
744 			else
745 				(void) printf("   ");
746 			(void) printf(":%02d", we->we_idle % 60);
747 		}
748 		(void) printf("\n");
749 	}
750 	return (0);
751 }
752 
753 static char *
754 interval(uint_t time, char *updown)
755 {
756 	static char resbuf[32];
757 	int days, hours, minutes;
758 
759 	if (time > 3*30*24*60*60) {
760 		(void) sprintf(resbuf, "   %s ??:??", updown);
761 		return (resbuf);
762 	}
763 	minutes = (time + 59) / 60;		/* round to minutes */
764 	hours = minutes / 60;
765 	minutes %= 60;
766 	days = hours / 24;
767 	hours %= 24;
768 	if (days > 0) {
769 		(void) sprintf(resbuf, "%s %2d+%02d:%02d",
770 		    updown, days, hours, minutes);
771 	} else {
772 		(void) sprintf(resbuf, "%s    %2d:%02d",
773 		    updown, hours, minutes);
774 	}
775 	return (resbuf);
776 }
777 #endif
778