xref: /titanic_50/usr/src/cmd/rpcsvc/rusers.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
23 
24 /*
25  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
30 /*
31  * University Copyright- Copyright (c) 1982, 1986, 1988
32  * The Regents of the University of California
33  * All Rights Reserved
34  *
35  * University Acknowledgment- Portions of this document are derived from
36  * software developed by the University of California, Berkeley, and its
37  * contributors.
38  */
39 
40 #pragma ident	"%Z%%M%	%I%	%E% SMI"
41 
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <unistd.h>
45 #include <sys/types.h>
46 #include <netconfig.h>
47 #include <netdir.h>
48 #include <rpc/rpc.h>
49 #include <rpcsvc/rusers.h>
50 #include <string.h>
51 #include <limits.h>
52 
53 #define	NMAX	12		/* These are used as field width specifiers */
54 #define	LMAX	8		/* when printing.			    */
55 #define	HMAX	16		/* "Logged in" host name. */
56 
57 #define	MACHINELEN 16		/* length of machine name printed out */
58 #define	NUMENTRIES 256
59 #define	min(a, b) ((a) < (b) ? (a) : (b))
60 
61 struct entry {
62 	int cnt;
63 	int idle;		/* set to INT_MAX if not present */
64 	char *machine;
65 	utmp_array users;
66 };
67 
68 static int curentry;
69 static int total_entries;
70 static struct entry *entry;
71 static int hflag;		/* host: sort by machine name */
72 static int iflag;		/* idle: sort by idle time */
73 static int uflag;		/* users: sort by number of users */
74 static int lflag;		/* print out long form */
75 static int aflag;		/* all: list all machines */
76 static int dflag;		/* debug: list only first n machines */
77 static int sorted;
78 static int debug;
79 static int debugcnt;
80 static char *nettype;
81 
82 static int hcompare(const struct entry *, const struct entry *);
83 static int icompare(const struct entry *, const struct entry *);
84 static int ucompare(const struct entry *, const struct entry *);
85 static int print_info(struct utmpidlearr *, const char *);
86 static int print_info_3(utmp_array *, const char *);
87 static int collectnames(void *, struct netbuf *, struct netconfig *);
88 static int collectnames_3(void *, struct netbuf *, struct netconfig *);
89 static void singlehost(char *);
90 static void printnames(void);
91 static void putline_2(char *, struct utmpidle *);
92 static void putline_3(char *, rusers_utmp *);
93 static void prttime(uint_t, char *);
94 static void usage(void);
95 
96 /*
97  * rusers [-ahilu] [host...]
98  */
99 int
main(int argc,char * argv[])100 main(int argc, char *argv[])
101 {
102 	int c;
103 	uint_t errflag = 0;
104 	uint_t single = 0;
105 	struct utmpidlearr utmpidlearr;
106 	utmp_array	utmp_array_res;
107 
108 	curentry = 0;
109 	total_entries = NUMENTRIES;
110 	entry = malloc(sizeof (struct entry) * total_entries);
111 
112 	while ((c = getopt(argc, argv, ":ad:hilun:")) != -1) {
113 		switch (c) {
114 		case 'a':
115 			aflag++;
116 			break;
117 		case 'd':
118 			dflag++;
119 			debug = atoi(optarg);
120 			(void) printf("Will collect %d responses.\n", debug);
121 			break;
122 		case 'h':
123 			hflag++;
124 			sorted++;
125 			if (iflag || uflag)
126 				errflag++;
127 			break;
128 		case 'i':
129 			iflag++;
130 			sorted++;
131 			if (hflag || uflag)
132 				errflag++;
133 			break;
134 		case 'u':
135 			uflag++;
136 			sorted++;
137 			if (hflag || iflag)
138 				errflag++;
139 			break;
140 		case 'l':
141 			lflag++;
142 			break;
143 		case ':':	/* required operand missing */
144 			errflag++;
145 			break;
146 		case 'n':
147 			nettype = optarg;
148 			break;
149 		default:
150 		case '?':	/* Unrecognized option */
151 			errflag++;
152 			break;
153 		}
154 	}
155 	if (errflag)
156 		usage();
157 
158 	for (; optind < argc; optind++) {
159 		single++;
160 		singlehost(argv[optind]);
161 	}
162 	if (single) {
163 		if (sorted)
164 			printnames();
165 		free(entry);
166 		exit(0);
167 	}
168 
169 	if (sorted) {
170 		(void) printf("Collecting responses...\n");
171 		(void) fflush(stdout);
172 	}
173 	utmp_array_res.utmp_array_val = NULL;
174 	utmp_array_res.utmp_array_len = 0;
175 	(void) printf("Sending broadcast for rusersd protocol version 3...\n");
176 	(void) rpc_broadcast(RUSERSPROG, RUSERSVERS_3,
177 		RUSERSPROC_NAMES, (xdrproc_t)xdr_void, NULL,
178 		(xdrproc_t)xdr_utmp_array, (char *)&utmp_array_res,
179 		(resultproc_t)collectnames_3, nettype);
180 	utmpidlearr.uia_arr = NULL;
181 	(void) printf("Sending broadcast for rusersd protocol version 2...\n");
182 	(void) rpc_broadcast(RUSERSPROG, RUSERSVERS_IDLE,
183 		RUSERSPROC_NAMES, (xdrproc_t)xdr_void, NULL,
184 		(xdrproc_t)xdr_utmpidlearr, (char *)&utmpidlearr,
185 		(resultproc_t)collectnames, nettype);
186 
187 	if (sorted)
188 		printnames();
189 
190 	free(entry);
191 	return (0);
192 }
193 
194 static void
singlehost(char * name)195 singlehost(char *name)
196 {
197 	enum clnt_stat err;
198 	struct utmpidlearr utmpidlearr;
199 	utmp_array	utmp_array_res;
200 
201 	if (curentry >= total_entries) {
202 		struct entry *tmp;
203 
204 		total_entries += NUMENTRIES;
205 		if ((tmp = realloc(entry, sizeof (struct entry)
206 						* total_entries)) == NULL)
207 			return;
208 		entry = tmp;
209 	}
210 	utmp_array_res.utmp_array_val = NULL;
211 	utmp_array_res.utmp_array_len = 0;
212 	err = rpc_call(name, RUSERSPROG, RUSERSVERS_3,
213 		RUSERSPROC_NAMES, (xdrproc_t)xdr_void, 0,
214 		(xdrproc_t)xdr_utmp_array, (char *)&utmp_array_res,
215 		nettype);
216 	if (err == RPC_SUCCESS) {
217 		(void) print_info_3(&utmp_array_res, name);
218 		return;
219 	}
220 	if (err == RPC_PROGVERSMISMATCH) {
221 		utmpidlearr.uia_arr = NULL;
222 		err = rpc_call(name, RUSERSPROG, RUSERSVERS_IDLE,
223 				RUSERSPROC_NAMES, (xdrproc_t)xdr_void, 0,
224 				(xdrproc_t)xdr_utmpidlearr,
225 				(char *)&utmpidlearr, nettype);
226 	}
227 	if (err != RPC_SUCCESS) {
228 		(void) fprintf(stderr, "%s: ", name);
229 		clnt_perrno(err);
230 		return;
231 	}
232 	(void) print_info(&utmpidlearr, name);
233 }
234 
235 /*
236  * Collect responses from RUSERSVERS_IDLE broadcast, convert to
237  * RUSERSVERS_3 format, and store in entry database.
238  */
239 static int
collectnames(void * resultsp,struct netbuf * raddrp,struct netconfig * nconf)240 collectnames(void *resultsp, struct netbuf *raddrp, struct netconfig *nconf)
241 {
242 	struct utmpidlearr utmpidlearr;
243 	struct entry *entryp, *lim;
244 	struct nd_hostservlist *hs;
245 	char host[MACHINELEN + 1];
246 
247 	utmpidlearr = *(struct utmpidlearr *)resultsp;
248 	if (utmpidlearr.uia_cnt < 1 && !aflag)
249 		return (0);
250 
251 	if (netdir_getbyaddr(nconf, &hs, raddrp)) {
252 #ifdef DEBUG
253 		netdir_perror("netdir_getbyaddr");
254 #endif
255 		/* netdir routine couldn't resolve addr;just print out uaddr */
256 		(void) sprintf(host, "%.*s", MACHINELEN,
257 						taddr2uaddr(nconf, raddrp));
258 	} else {
259 		(void) sprintf(host, "%.*s", MACHINELEN,
260 						hs->h_hostservs->h_host);
261 		netdir_free((char *)hs, ND_HOSTSERVLIST);
262 	}
263 	/*
264 	 * need to realloc more space if we have more than 256 machines
265 	 * that respond to broadcast
266 	 */
267 	if (curentry >= total_entries) {
268 		struct entry *tmp;
269 
270 		total_entries += NUMENTRIES;
271 		if ((tmp = realloc(entry, sizeof (struct entry)
272 						* total_entries)) == NULL)
273 			return (1);
274 		entry = tmp;
275 	}
276 
277 
278 	/*
279 	 * weed out duplicates
280 	 */
281 	lim = entry + curentry;
282 	for (entryp = entry; entryp < lim; entryp++) {
283 		if (strcmp(entryp->machine, host) == 0)
284 			return (0);
285 	}
286 	return (print_info((struct utmpidlearr *)resultsp, host));
287 }
288 
289 static int
print_info(struct utmpidlearr * utmpidlearrp,const char * name)290 print_info(struct utmpidlearr *utmpidlearrp, const char *name)
291 {
292 	utmp_array *iconvert;
293 	int i, cnt, minidle;
294 	char host[MACHINELEN + 1];
295 	char username[NMAX + 1];
296 
297 	cnt = utmpidlearrp->uia_cnt;
298 	(void) sprintf(host, "%.*s", MACHINELEN, name);
299 
300 	/*
301 	 * if raw, print this entry out immediately
302 	 * otherwise store for later sorting
303 	 */
304 	if (!sorted) {
305 		if (lflag && (cnt > 0))
306 			for (i = 0; i < cnt; i++)
307 				putline_2(host, utmpidlearrp->uia_arr[i]);
308 		else {
309 		    (void) printf("%-*.*s", MACHINELEN, MACHINELEN, host);
310 		    for (i = 0; i < cnt; i++) {
311 			(void) strlcpy(username,
312 				    utmpidlearrp->uia_arr[i]->ui_utmp.ut_name,
313 				    NMAX + 1);
314 			(void) printf(" %.*s", NMAX, username);
315 		    }
316 		    (void) printf("\n");
317 		}
318 		/* store just the name */
319 		entry[curentry].machine = malloc(MACHINELEN + 1);
320 		if (entry[curentry].machine == NULL) {
321 			(void) fprintf(stderr, "Ran out of memory - exiting\n");
322 			exit(1);
323 		}
324 		(void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
325 		entry[curentry++].cnt = 0;
326 		if (dflag && (++debugcnt >= debug))
327 			return (1);
328 		return (0);
329 	}
330 	entry[curentry].machine = malloc(MACHINELEN + 1);
331 	if (entry[curentry].machine == NULL) {
332 		(void) fprintf(stderr, "Ran out of memory - exiting\n");
333 		exit(1);
334 	}
335 	(void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
336 	entry[curentry].cnt = cnt;
337 	iconvert = &entry[curentry].users;
338 	iconvert->utmp_array_len = cnt;
339 	iconvert->utmp_array_val = malloc(cnt * sizeof (rusers_utmp));
340 	minidle = INT_MAX;
341 	for (i = 0; i < cnt; i++) {
342 		iconvert->utmp_array_val[i].ut_user =
343 			strdup(utmpidlearrp->uia_arr[i]->ui_utmp.ut_name);
344 		iconvert->utmp_array_val[i].ut_line =
345 			strdup(utmpidlearrp->uia_arr[i]->ui_utmp.ut_line);
346 		iconvert->utmp_array_val[i].ut_host =
347 			strdup(utmpidlearrp->uia_arr[i]->ui_utmp.ut_host);
348 		iconvert->utmp_array_val[i].ut_time =
349 			utmpidlearrp->uia_arr[i]->ui_utmp.ut_time;
350 		iconvert->utmp_array_val[i].ut_idle =
351 			utmpidlearrp->uia_arr[i]->ui_idle;
352 		minidle = min(minidle, utmpidlearrp->uia_arr[i]->ui_idle);
353 	}
354 	entry[curentry].idle = minidle;
355 	curentry++;
356 	if (dflag && (++debugcnt >= debug))
357 		return (1);
358 	return (0);
359 }
360 
361 
362 /*
363  * Collect responses from RUSERSVERS_3 broadcast.
364  */
365 static int
collectnames_3(void * resultsp,struct netbuf * raddrp,struct netconfig * nconf)366 collectnames_3(void *resultsp, struct netbuf *raddrp, struct netconfig *nconf)
367 {
368 	utmp_array *uap;
369 	struct entry *entryp, *lim;
370 	struct nd_hostservlist *hs;
371 	char host[MACHINELEN + 1];
372 
373 	uap = (utmp_array *)resultsp;
374 	if (uap->utmp_array_len < 1 && !aflag)
375 		return (0);
376 
377 	if (netdir_getbyaddr(nconf, &hs, raddrp)) {
378 #ifdef DEBUG
379 	netdir_perror("netdir_getbyaddr");
380 #endif
381 		/* netdir routine couldn't resolve addr;just print out uaddr */
382 		(void) sprintf(host, "%.*s", MACHINELEN,
383 						taddr2uaddr(nconf, raddrp));
384 	} else {
385 		(void) sprintf(host, "%.*s", MACHINELEN,
386 						hs->h_hostservs->h_host);
387 		netdir_free((char *)hs, ND_HOSTSERVLIST);
388 	}
389 
390 	/*
391 	 * need to realloc more space if we have more than 256 machines
392 	 * that respond to broadcast
393 	 */
394 	if (curentry >= total_entries) {
395 		struct entry *tmp;
396 
397 		total_entries += NUMENTRIES;
398 		if ((tmp = realloc(entry, sizeof (struct entry)
399 						* total_entries)) == NULL)
400 			return (1);
401 		entry = tmp;
402 	}
403 
404 
405 	/*
406 	 * weed out duplicates
407 	 */
408 	lim = entry + curentry;
409 	for (entryp = entry; entryp < lim; entryp++) {
410 		if (strcmp(entryp->machine, host) == 0)
411 			return (0);
412 	}
413 	return (print_info_3(uap, host));
414 }
415 
416 static int
print_info_3(utmp_array * uap,const char * name)417 print_info_3(utmp_array *uap, const char *name)
418 {
419 	int i, cnt, minidle;
420 	char host[MACHINELEN + 1];
421 
422 	cnt = uap->utmp_array_len;
423 
424 	(void) sprintf(host, "%.*s", MACHINELEN, name);
425 
426 	/*
427 	 * if raw, print this entry out immediately
428 	 * otherwise store for later sorting
429 	 */
430 	if (!sorted) {
431 		if (lflag && (cnt > 0))
432 			for (i = 0; i < cnt; i++)
433 				putline_3(host, &uap->utmp_array_val[i]);
434 		else {
435 			(void) printf("%-*.*s", MACHINELEN, MACHINELEN, host);
436 			for (i = 0; i < cnt; i++)
437 				(void) printf(" %.*s", NMAX,
438 				    uap->utmp_array_val[i].ut_user);
439 			(void) printf("\n");
440 		}
441 		/* store just the name */
442 		entry[curentry].machine = malloc(MACHINELEN + 1);
443 		if (entry[curentry].machine == NULL) {
444 			(void) fprintf(stderr, "Ran out of memory - exiting\n");
445 			exit(1);
446 		}
447 		(void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
448 		entry[curentry++].cnt = 0;
449 		if (dflag && (++debugcnt >= debug))
450 			return (1);
451 		return (0);
452 	}
453 
454 	entry[curentry].machine = malloc(MACHINELEN + 1);
455 	if (entry[curentry].machine == NULL) {
456 		(void) fprintf(stderr, "Ran out of memory - exiting\n");
457 		exit(1);
458 	}
459 	(void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
460 	entry[curentry].cnt = cnt;
461 	entry[curentry].users.utmp_array_len = cnt;
462 	entry[curentry].users.utmp_array_val = malloc(cnt *
463 		sizeof (rusers_utmp));
464 	minidle = INT_MAX;
465 	for (i = 0; i < cnt; i++) {
466 		entry[curentry].users.utmp_array_val[i].ut_user =
467 			strdup(uap->utmp_array_val[i].ut_user);
468 		entry[curentry].users.utmp_array_val[i].ut_line =
469 			strdup(uap->utmp_array_val[i].ut_line);
470 		entry[curentry].users.utmp_array_val[i].ut_host =
471 			strdup(uap->utmp_array_val[i].ut_host);
472 		entry[curentry].users.utmp_array_val[i].ut_time =
473 			uap->utmp_array_val[i].ut_time;
474 		entry[curentry].users.utmp_array_val[i].ut_idle =
475 			uap->utmp_array_val[i].ut_idle;
476 		minidle = min(minidle, uap->utmp_array_val[i].ut_idle);
477 	}
478 	entry[curentry].idle = minidle;
479 	curentry++;
480 	if (dflag && (++debugcnt >= debug))
481 		return (1);
482 	return (0);
483 }
484 
485 static void
printnames(void)486 printnames(void)
487 {
488 	int i, j;
489 	int (*compare)(const void *, const void *);
490 
491 	/* the name of the machine should already be in the structure */
492 	if (iflag)
493 		compare = (int (*)(const void *, const void *))icompare;
494 	else if (hflag)
495 		compare = (int (*)(const void *, const void *))hcompare;
496 	else
497 		compare = (int (*)(const void *, const void *))ucompare;
498 	qsort(entry, curentry, sizeof (struct entry), compare);
499 	for (i = 0; i < curentry; i++) {
500 		if (!lflag || (entry[i].cnt < 1)) {
501 			(void) printf("%-*.*s", MACHINELEN,
502 					MACHINELEN, entry[i].machine);
503 			for (j = 0; j < entry[i].cnt; j++)
504 				(void) printf(" %.*s", NMAX,
505 				    entry[i].users.utmp_array_val[j].ut_user);
506 			(void) printf("\n");
507 		} else {
508 			for (j = 0; j < entry[i].cnt; j++)
509 				putline_3(entry[i].machine,
510 					&entry[i].users.utmp_array_val[j]);
511 		}
512 	}
513 }
514 
515 static int
hcompare(const struct entry * a,const struct entry * b)516 hcompare(const struct entry *a, const struct entry *b)
517 {
518 	return (strcmp(a->machine, b->machine));
519 }
520 
521 static int
ucompare(const struct entry * a,const struct entry * b)522 ucompare(const struct entry *a, const struct entry *b)
523 {
524 	return (b->cnt - a->cnt);
525 }
526 
527 static int
icompare(const struct entry * a,const struct entry * b)528 icompare(const struct entry *a, const struct entry *b)
529 {
530 	return (a->idle - b->idle);
531 }
532 
533 static void
putline_2(char * host,struct utmpidle * uip)534 putline_2(char *host, struct utmpidle *uip)
535 {
536 	char *cbuf;
537 	struct ru_utmp *up;
538 	char buf[100];
539 
540 	up = &uip->ui_utmp;
541 #define	NAMEMAX	((sizeof (up->ut_name) < NMAX) ? NMAX : sizeof (up->ut_name))
542 #define	NAMEMIN	((sizeof (up->ut_name) > NMAX) ? NMAX : sizeof (up->ut_name))
543 	/* Try and align this up nicely */
544 #define	LINEMAX	sizeof (up->ut_line)
545 #define	HOSTMAX	sizeof (up->ut_host)
546 	/*
547 	 * We copy the strings into a buffer because they aren't strictly
548 	 * speaking strings but byte arrays (and they may not have a
549 	 * terminating NULL.
550 	 */
551 
552 	(void) strncpy(buf, up->ut_name, NAMEMAX);
553 	buf[NAMEMIN] = '\0';
554 	(void) printf("%-*.*s ", NAMEMAX, NAMEMAX, buf);
555 
556 	(void) strcpy(buf, host);
557 	(void) strcat(buf, ":");
558 	(void) strncat(buf, up->ut_line, LINEMAX);
559 	buf[MACHINELEN+LINEMAX] = '\0';
560 	(void) printf("%-*.*s", MACHINELEN+LINEMAX, MACHINELEN+LINEMAX, buf);
561 
562 	cbuf = (char *)ctime(&up->ut_time);
563 	(void) printf("  %.12s  ", cbuf+4);
564 	if (uip->ui_idle == INT_MAX)
565 		(void) printf("    ??");
566 	else
567 		prttime(uip->ui_idle, "");
568 	if (up->ut_host[0]) {
569 		(void) strncpy(buf, up->ut_host, HOSTMAX);
570 		buf[HOSTMAX] = '\0';
571 		(void) printf(" (%.*s)", HOSTMAX, buf);
572 	}
573 	(void) putchar('\n');
574 }
575 
576 static void
putline_3(char * host,rusers_utmp * rup)577 putline_3(char *host, rusers_utmp *rup)
578 {
579 	char *cbuf;
580 	char buf[100];
581 
582 	(void) printf("%-*.*s ", NMAX, NMAX, rup->ut_user);
583 	(void) strcpy(buf, host);
584 	(void) strcat(buf, ":");
585 	(void) strncat(buf, rup->ut_line, LMAX);
586 	(void) printf("%-*.*s", MACHINELEN+LMAX, MACHINELEN+LMAX, buf);
587 
588 	cbuf = (char *)ctime((time_t *)&rup->ut_time);
589 	(void) printf("  %.12s  ", cbuf+4);
590 	if (rup->ut_idle == INT_MAX)
591 		(void) printf("    ??");
592 	else
593 		prttime(rup->ut_idle, "");
594 	if (rup->ut_host[0])
595 		(void) printf(" (%.*s)", HMAX, rup->ut_host);
596 	(void) putchar('\n');
597 }
598 
599 /*
600  * prttime prints a time in hours and minutes.
601  * The character string tail is printed at the end, obvious
602  * strings to pass are "", " ", or "am".
603  */
604 static void
prttime(uint_t tim,char * tail)605 prttime(uint_t tim, char *tail)
606 {
607 	int didhrs = 0;
608 
609 	if (tim >= 60) {
610 		(void) printf("%3d:", tim/60);
611 		didhrs++;
612 	} else {
613 		(void) printf("    ");
614 	}
615 	tim %= 60;
616 	if (tim > 0 || didhrs) {
617 		(void) printf(didhrs && tim < 10 ? "%02d" : "%2d", tim);
618 	} else {
619 		(void) printf("  ");
620 	}
621 	(void) printf("%s", tail);
622 }
623 
624 #ifdef DEBUG
625 /*
626  * for debugging
627  */
628 int
printit(int i)629 printit(int i)
630 {
631 	int j, v;
632 
633 	(void) printf("%12.12s: ", entry[i].machine);
634 	if (entry[i].cnt) {
635 		putline_3(entry[i].machine, &entry[i].users.utmp_array_val[0]);
636 		for (j = 1; j < entry[i].cnt; j++) {
637 			(void) printf("\t");
638 			putline_3(entry[i].machine,
639 				&entry[i].users.utmp_array_val[j]);
640 		}
641 	} else
642 		(void) printf("\n");
643 }
644 #endif
645 
646 static void
usage(void)647 usage(void)
648 {
649 	(void) fprintf(stderr, "Usage: rusers [-ahilu] [host ...]\n");
650 	free(entry);
651 	exit(1);
652 }
653