xref: /netbsd-src/usr.bin/getent/getent.c (revision 666c820abadac0c0214698082b204c39c34b5b01)
1 /*	$NetBSD: getent.c,v 1.21 2024/11/21 16:33:37 kre Exp $	*/
2 
3 /*-
4  * Copyright (c) 2004-2006 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: getent.c,v 1.21 2024/11/21 16:33:37 kre Exp $");
35 #endif /* not lint */
36 
37 #include <sys/socket.h>
38 
39 #include <assert.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <grp.h>
43 #include <limits.h>
44 #include <netdb.h>
45 #include <netgroup.h>
46 #include <pwd.h>
47 #include <stdio.h>
48 #include <stdarg.h>
49 #include <stdbool.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <paths.h>
54 #include <err.h>
55 
56 #include <arpa/inet.h>
57 #include <arpa/nameser.h>
58 
59 #include <net/if.h>
60 #include <net/if_ether.h>
61 
62 #include <netinet/in.h>		/* for INET6_ADDRSTRLEN */
63 
64 #include <rpc/rpcent.h>
65 
66 #include <disktab.h>
67 
68 static int	usage(void) __attribute__((__noreturn__));
69 static int	parsenum(const char *, unsigned long *);
70 static int	disktab(int, char *[]);
71 static int	gettytab(int, char *[]);
72 static int	ethers(int, char *[]);
73 static int	group(int, char *[]);
74 static int	hosts(int, char *[]);
75 static int	netgroup(int, char *[]);
76 static int	networks(int, char *[]);
77 static int	passwd(int, char *[]);
78 static int	printcap(int, char *[]);
79 static int	protocols(int, char *[]);
80 static int	rpc(int, char *[]);
81 static int	services(int, char *[]);
82 static int	shells(int, char *[]);
83 
84 enum {
85 	RV_OK		= 0,
86 	RV_USAGE	= 1,
87 	RV_NOTFOUND	= 2,
88 	RV_NOENUM	= 3
89 };
90 
91 static struct getentdb {
92 	const char	*name;
93 	int		(*callback)(int, char *[]);
94 } databases[] = {
95 	{	"disktab",	disktab,	},
96 	{	"ethers",	ethers,		},
97 	{	"gettytab",	gettytab,	},
98 	{	"group",	group,		},
99 	{	"hosts",	hosts,		},
100 	{	"netgroup",	netgroup,	},
101 	{	"networks",	networks,	},
102 	{	"passwd",	passwd,		},
103 	{	"printcap",	printcap,	},
104 	{	"protocols",	protocols,	},
105 	{	"rpc",		rpc,		},
106 	{	"services",	services,	},
107 	{	"shells",	shells,		},
108 
109 	{	NULL,		NULL,		},
110 };
111 
112 
113 int
114 main(int argc, char *argv[])
115 {
116 	struct getentdb	*curdb;
117 
118 	setprogname(argv[0]);
119 
120 	if (argc < 2)
121 		usage();
122 	for (curdb = databases; curdb->name != NULL; curdb++)
123 		if (strcmp(curdb->name, argv[1]) == 0)
124 			return (*curdb->callback)(argc, argv);
125 
126 	warn("Unknown database `%s'", argv[1]);
127 	usage();
128 	/* NOTREACHED */
129 }
130 
131 static int
132 usage(void)
133 {
134 	struct getentdb	*curdb;
135 	size_t i;
136 
137 	(void)fprintf(stderr, "Usage: %s database [key ...]\n",
138 	    getprogname());
139 	(void)fprintf(stderr, "\tdatabase may be one of:");
140 	for (i = 0, curdb = databases; curdb->name != NULL; curdb++, i++) {
141 		if (i % 7 == 0)
142 			(void)fputs("\n\t\t", stderr);
143 		(void)fprintf(stderr, "%s%s", i % 7 == 0 ? "" : " ",
144 		    curdb->name);
145 	}
146 	(void)fprintf(stderr, "\n");
147 	exit(RV_USAGE);
148 	/* NOTREACHED */
149 }
150 
151 static int
152 parsenum(const char *word, unsigned long *result)
153 {
154 	unsigned long	num;
155 	char		*ep;
156 
157 	assert(word != NULL);
158 	assert(result != NULL);
159 
160 	if (!isdigit((unsigned char)word[0]))
161 		return 0;
162 	errno = 0;
163 	num = strtoul(word, &ep, 10);
164 	if (num == ULONG_MAX && errno == ERANGE)
165 		return 0;
166 	if (*ep != '\0')
167 		return 0;
168 	*result = num;
169 	return 1;
170 }
171 
172 /*
173  * printfmtstrings --
174  *	vprintf(format, ...),
175  *	then the aliases (beginning with prefix, separated by sep),
176  *	then a newline
177  */
178 static __printflike(4, 5) void
179 printfmtstrings(char *strings[], const char *prefix, const char *sep,
180     const char *fmt, ...)
181 {
182 	va_list		ap;
183 	const char	*curpref;
184 	size_t		i;
185 
186 	va_start(ap, fmt);
187 	(void)vprintf(fmt, ap);
188 	va_end(ap);
189 
190 	curpref = prefix;
191 	for (i = 0; strings[i] != NULL; i++) {
192 		(void)printf("%s%s", curpref, strings[i]);
193 		curpref = sep;
194 	}
195 	(void)printf("\n");
196 }
197 
198 
199 		/*
200 		 * ethers
201 		 */
202 
203 static int
204 ethers(int argc, char *argv[])
205 {
206 	char		hostname[MAXHOSTNAMELEN + 1], *hp;
207 	struct ether_addr ea, *eap;
208 	int		i, rv;
209 
210 	assert(argc > 1);
211 	assert(argv != NULL);
212 
213 #define ETHERSPRINT	(void)printf("%-17s  %s\n", ether_ntoa(eap), hp)
214 
215 	rv = RV_OK;
216 	if (argc == 2) {
217 		warnx("Enumeration not supported on ethers");
218 		rv = RV_NOENUM;
219 	} else {
220 		for (i = 2; i < argc; i++) {
221 			if ((eap = ether_aton(argv[i])) == NULL) {
222 				eap = &ea;
223 				hp = argv[i];
224 				if (ether_hostton(hp, eap) != 0) {
225 					rv = RV_NOTFOUND;
226 					break;
227 				}
228 			} else {
229 				hp = hostname;
230 				if (ether_ntohost(hp, eap) != 0) {
231 					rv = RV_NOTFOUND;
232 					break;
233 				}
234 			}
235 			ETHERSPRINT;
236 		}
237 	}
238 	return rv;
239 }
240 
241 		/*
242 		 * group
243 		 */
244 
245 static int
246 group(int argc, char *argv[])
247 {
248 	struct group	*gr;
249 	unsigned long	id;
250 	int		i, rv;
251 
252 	assert(argc > 1);
253 	assert(argv != NULL);
254 
255 #define GROUPPRINT	printfmtstrings(gr->gr_mem, "", ",", "%s:%s:%u:", \
256 			    gr->gr_name, gr->gr_passwd, gr->gr_gid)
257 
258 	(void)setgroupent(1);
259 	rv = RV_OK;
260 	if (argc == 2) {
261 		while ((gr = getgrent()) != NULL)
262 			GROUPPRINT;
263 	} else {
264 		for (i = 2; i < argc; i++) {
265 			if (parsenum(argv[i], &id))
266 				gr = getgrgid((gid_t)id);
267 			else
268 				gr = getgrnam(argv[i]);
269 			if (gr != NULL)
270 				GROUPPRINT;
271 			else {
272 				rv = RV_NOTFOUND;
273 				break;
274 			}
275 		}
276 	}
277 	endgrent();
278 	return rv;
279 }
280 
281 
282 		/*
283 		 * hosts
284 		 */
285 
286 static void
287 hostsprint(const struct hostent *he)
288 {
289 	char	buf[INET6_ADDRSTRLEN];
290 
291 	assert(he != NULL);
292 	if (inet_ntop(he->h_addrtype, he->h_addr, buf, sizeof(buf)) == NULL)
293 		(void)strlcpy(buf, "# unknown", sizeof(buf));
294 	printfmtstrings(he->h_aliases, "  ", " ", "%-16s  %s", buf, he->h_name);
295 }
296 
297 static int
298 hosts(int argc, char *argv[])
299 {
300 	struct hostent	*he;
301 	char		addr[IN6ADDRSZ];
302 	int		i, rv;
303 
304 	assert(argc > 1);
305 	assert(argv != NULL);
306 
307 	sethostent(1);
308 	rv = RV_OK;
309 	if (argc == 2) {
310 		while ((he = gethostent()) != NULL)
311 			hostsprint(he);
312 	} else {
313 		for (i = 2; i < argc; i++) {
314 			if (inet_pton(AF_INET6, argv[i], (void *)addr) > 0)
315 				he = gethostbyaddr(addr, IN6ADDRSZ, AF_INET6);
316 			else if (inet_pton(AF_INET, argv[i], (void *)addr) > 0)
317 				he = gethostbyaddr(addr, INADDRSZ, AF_INET);
318 			else
319 				he = gethostbyname(argv[i]);
320 			if (he != NULL)
321 				hostsprint(he);
322 			else {
323 				rv = RV_NOTFOUND;
324 				break;
325 			}
326 		}
327 	}
328 	endhostent();
329 	return rv;
330 }
331 
332 		/*
333 		 * netgroup
334 		 */
335 static int
336 netgroup(int argc, char *argv[])
337 {
338 	int		rv, i;
339 	bool		first;
340 	const char	*host, *user, *domain;
341 
342 	assert(argc > 1);
343 	assert(argv != NULL);
344 
345 #define NETGROUPPRINT(s)	(((s) != NULL) ? (s) : "")
346 
347 	rv = RV_OK;
348 	if (argc == 2) {
349 		warnx("Enumeration not supported on netgroup");
350 		rv = RV_NOENUM;
351 	} else {
352 		for (i = 2; i < argc; i++) {
353 			setnetgrent(argv[i]);
354 			first = true;
355 			while (getnetgrent(&host, &user, &domain) != 0) {
356 				if (first) {
357 					first = false;
358 					(void)fputs(argv[i], stdout);
359 				}
360 				(void)printf(" (%s,%s,%s)",
361 				    NETGROUPPRINT(host),
362 				    NETGROUPPRINT(user),
363 				    NETGROUPPRINT(domain));
364 			}
365 			if (!first)
366 				(void)putchar('\n');
367 			endnetgrent();
368 		}
369 	}
370 
371 	return rv;
372 }
373 
374 		/*
375 		 * networks
376 		 */
377 
378 static void
379 networksprint(const struct netent *ne)
380 {
381 	char		buf[INET6_ADDRSTRLEN];
382 	struct	in_addr	ianet;
383 
384 	assert(ne != NULL);
385 	ianet = inet_makeaddr(ne->n_net, 0);
386 	if (inet_ntop(ne->n_addrtype, &ianet, buf, sizeof(buf)) == NULL)
387 		(void)strlcpy(buf, "# unknown", sizeof(buf));
388 	printfmtstrings(ne->n_aliases, "  ", " ", "%-16s  %s", ne->n_name, buf);
389 }
390 
391 static int
392 networks(int argc, char *argv[])
393 {
394 	struct netent	*ne;
395 	in_addr_t	net;
396 	int		i, rv;
397 
398 	assert(argc > 1);
399 	assert(argv != NULL);
400 
401 	setnetent(1);
402 	rv = RV_OK;
403 	if (argc == 2) {
404 		while ((ne = getnetent()) != NULL)
405 			networksprint(ne);
406 	} else {
407 		for (i = 2; i < argc; i++) {
408 			net = inet_network(argv[i]);
409 			if (net != INADDR_NONE)
410 				ne = getnetbyaddr(net, AF_INET);
411 			else
412 				ne = getnetbyname(argv[i]);
413 			if (ne != NULL)
414 				networksprint(ne);
415 			else {
416 				rv = RV_NOTFOUND;
417 				break;
418 			}
419 		}
420 	}
421 	endnetent();
422 	return rv;
423 }
424 
425 
426 		/*
427 		 * passwd
428 		 */
429 
430 static int
431 passwd(int argc, char *argv[])
432 {
433 	struct passwd	*pw;
434 	unsigned long	id;
435 	int		i, rv;
436 
437 	assert(argc > 1);
438 	assert(argv != NULL);
439 
440 #define PASSWDPRINT	(void)printf("%s:%s:%u:%u:%s:%s:%s\n", \
441 			    pw->pw_name, pw->pw_passwd, pw->pw_uid, \
442 			    pw->pw_gid, pw->pw_gecos, pw->pw_dir, pw->pw_shell)
443 
444 	(void)setpassent(1);
445 	rv = RV_OK;
446 	if (argc == 2) {
447 		while ((pw = getpwent()) != NULL)
448 			PASSWDPRINT;
449 	} else {
450 		for (i = 2; i < argc; i++) {
451 			if (parsenum(argv[i], &id))
452 				pw = getpwuid((uid_t)id);
453 			else
454 				pw = getpwnam(argv[i]);
455 			if (pw != NULL)
456 				PASSWDPRINT;
457 			else {
458 				rv = RV_NOTFOUND;
459 				break;
460 			}
461 		}
462 	}
463 	endpwent();
464 	return rv;
465 }
466 
467 static char *
468 mygetent(const char * const * db_array, const char *name)
469 {
470 	char *buf = NULL;
471 	int error;
472 
473 	switch (error = cgetent(&buf, db_array, name)) {
474 	case -3:
475 		warnx("tc= loop in record `%s' in `%s'", name, db_array[0]);
476 		break;
477 	case -2:
478 		warn("system error fetching record `%s' in `%s'", name,
479 		    db_array[0]);
480 		break;
481 	case -1:
482 	case 0:
483 		break;
484 	case 1:
485 		warnx("tc= reference not found in record for `%s' in `%s'",
486 		    name, db_array[0]);
487 		break;
488 	default:
489 		warnx("unknown error %d in record `%s' in `%s'", error, name,
490 		    db_array[0]);
491 		break;
492 	}
493 	return buf;
494 }
495 
496 static char *
497 mygetone(const char * const * db_array, int first)
498 {
499 	char *buf = NULL;
500 	int error;
501 
502 	switch (error = (first ? cgetfirst : cgetnext)(&buf, db_array)) {
503 	case -2:
504 		warnx("tc= loop in `%s'", db_array[0]);
505 		break;
506 	case -1:
507 		warn("system error fetching record in `%s'", db_array[0]);
508 		break;
509 	case 0:
510 	case 1:
511 		break;
512 	case 2:
513 		warnx("tc= reference not found in `%s'", db_array[0]);
514 		break;
515 	default:
516 		warnx("unknown error %d in `%s'", error, db_array[0]);
517 		break;
518 	}
519 	return buf;
520 }
521 
522 static void
523 capprint(const char *cap)
524 {
525 	char *c = strchr(cap, ':');
526 	if (c)
527 		if (c == cap)
528 			(void)printf("true\n");
529 		else {
530 			int l = (int)(c - cap);
531 			(void)printf("%*.*s\n", l, l, cap);
532 		}
533 	else
534 		(void)printf("%s\n", cap);
535 }
536 
537 static void
538 prettyprint(char *b)
539 {
540 #define TERMWIDTH 65
541 	int did = 0;
542 	size_t len;
543 	char *s, c;
544 
545 	for (;;) {
546 		len = strlen(b);
547 		if (len <= TERMWIDTH) {
548 done:
549 			if (did)
550 				printf("\t:");
551 			printf("%s\n", b);
552 			return;
553 		}
554 		for (s = b + TERMWIDTH; s > b && *s != ':'; s--)
555 			continue;
556 		if (*s++ != ':')
557 			goto done;
558 		c = *s;
559 		*s = '\0';
560 		if (did)
561 			printf("\t:");
562 		did++;
563 		printf("%s\\\n", b);
564 		*s = c;
565 		b = s;
566 	}
567 }
568 
569 static void
570 handleone(const char * const *db_array, char *b, int recurse, int pretty,
571     int level)
572 {
573 	char *tc;
574 
575 	if (level && pretty)
576 		printf("\n");
577 	if (pretty)
578 		prettyprint(b);
579 	else
580 		printf("%s\n", b);
581 	if (!recurse || cgetstr(b, "tc", &tc) <= 0)
582 		return;
583 
584 	b = mygetent(db_array, tc);
585 	free(tc);
586 
587 	if (b == NULL)
588 		return;
589 
590 	handleone(db_array, b, recurse, pretty, ++level);
591 	free(b);
592 }
593 
594 static int
595 handlecap(const char *db, int argc, char *argv[])
596 {
597 	static const char sfx[] = "=#:";
598 	const char *db_array[] = { db, NULL };
599 	char	*b, *cap;
600 	int	i, rv, c;
601 	size_t	j;
602 	int	expand = 1, recurse = 0, pretty = 0;
603 
604 	assert(argc > 1);
605 	assert(argv != NULL);
606 
607 	argc--;
608 	argv++;
609 	while ((c = getopt(argc, argv, "pnr")) != -1)
610 		switch (c) {
611 		case 'n':
612 			expand = 0;
613 			break;
614 		case 'r':
615 			expand = 0;
616 			recurse = 1;
617 			break;
618 		case 'p':
619 			pretty = 1;
620 			break;
621 		default:
622 			usage();
623 			break;
624 		}
625 
626 	argc -= optind;
627 	argv += optind;
628 	csetexpandtc(expand);
629 	rv = RV_OK;
630 	if (argc == 0) {
631 		for (b = mygetone(db_array, 1); b; b = mygetone(db_array, 0)) {
632 			handleone(db_array, b, recurse, pretty, 0);
633 			free(b);
634 		}
635 	} else {
636 		if ((b = mygetent(db_array, argv[0])) == NULL)
637 			return RV_NOTFOUND;
638 		if (argc == 1)
639 			handleone(db_array, b, recurse, pretty, 0);
640 		else {
641 			for (i = 2; i < argc; i++) {
642 				for (j = 0; j < sizeof(sfx) - 1; j++) {
643 					cap = cgetcap(b, argv[i], sfx[j]);
644 					if (cap) {
645 						capprint(cap);
646 						break;
647 					}
648 				}
649 				if (j == sizeof(sfx) - 1)
650 					printf("false\n");
651 			}
652 		}
653 		free(b);
654 	}
655 	return rv;
656 }
657 
658 		/*
659 		 * gettytab
660 		 */
661 
662 static int
663 gettytab(int argc, char *argv[])
664 {
665 	return handlecap(_PATH_GETTYTAB, argc, argv);
666 }
667 
668 		/*
669 		 * printcap
670 		 */
671 
672 static int
673 printcap(int argc, char *argv[])
674 {
675 	return handlecap(_PATH_PRINTCAP, argc, argv);
676 }
677 
678 		/*
679 		 * disktab
680 		 */
681 
682 static int
683 disktab(int argc, char *argv[])
684 {
685 	return handlecap(_PATH_DISKTAB, argc, argv);
686 }
687 
688 		/*
689 		 * protocols
690 		 */
691 
692 static int
693 protocols(int argc, char *argv[])
694 {
695 	struct protoent	*pe;
696 	unsigned long	id;
697 	int		i, rv;
698 
699 	assert(argc > 1);
700 	assert(argv != NULL);
701 
702 #define PROTOCOLSPRINT	printfmtstrings(pe->p_aliases, "  ", " ", \
703 			    "%-16s  %5d", pe->p_name, pe->p_proto)
704 
705 	setprotoent(1);
706 	rv = RV_OK;
707 	if (argc == 2) {
708 		while ((pe = getprotoent()) != NULL)
709 			PROTOCOLSPRINT;
710 	} else {
711 		for (i = 2; i < argc; i++) {
712 			if (parsenum(argv[i], &id))
713 				pe = getprotobynumber((int)id);
714 			else
715 				pe = getprotobyname(argv[i]);
716 			if (pe != NULL)
717 				PROTOCOLSPRINT;
718 			else {
719 				rv = RV_NOTFOUND;
720 				break;
721 			}
722 		}
723 	}
724 	endprotoent();
725 	return rv;
726 }
727 
728 		/*
729 		 * rpc
730 		 */
731 
732 static int
733 rpc(int argc, char *argv[])
734 {
735 	struct rpcent	*re;
736 	unsigned long	id;
737 	int		i, rv;
738 
739 	assert(argc > 1);
740 	assert(argv != NULL);
741 
742 #define RPCPRINT	printfmtstrings(re->r_aliases, "  ", " ", \
743 				"%-16s  %6d", \
744 				re->r_name, re->r_number)
745 
746 	setrpcent(1);
747 	rv = RV_OK;
748 	if (argc == 2) {
749 		while ((re = getrpcent()) != NULL)
750 			RPCPRINT;
751 	} else {
752 		for (i = 2; i < argc; i++) {
753 			if (parsenum(argv[i], &id))
754 				re = getrpcbynumber((int)id);
755 			else
756 				re = getrpcbyname(argv[i]);
757 			if (re != NULL)
758 				RPCPRINT;
759 			else {
760 				rv = RV_NOTFOUND;
761 				break;
762 			}
763 		}
764 	}
765 	endrpcent();
766 	return rv;
767 }
768 
769 		/*
770 		 * services
771 		 */
772 
773 static int
774 services(int argc, char *argv[])
775 {
776 	struct servent	*se;
777 	unsigned long	id;
778 	char		*proto;
779 	int		i, rv;
780 
781 	assert(argc > 1);
782 	assert(argv != NULL);
783 
784 #define SERVICESPRINT	printfmtstrings(se->s_aliases, "  ", " ", \
785 			    "%-16s  %5d/%s", \
786 			    se->s_name, ntohs(se->s_port), se->s_proto)
787 
788 	setservent(1);
789 	rv = RV_OK;
790 	if (argc == 2) {
791 		while ((se = getservent()) != NULL)
792 			SERVICESPRINT;
793 	} else {
794 		for (i = 2; i < argc; i++) {
795 			proto = strchr(argv[i], '/');
796 			if (proto != NULL)
797 				*proto++ = '\0';
798 			if (parsenum(argv[i], &id))
799 				se = getservbyport(htons(id), proto);
800 			else
801 				se = getservbyname(argv[i], proto);
802 			if (se != NULL)
803 				SERVICESPRINT;
804 			else {
805 				rv = RV_NOTFOUND;
806 				break;
807 			}
808 		}
809 	}
810 	endservent();
811 	return rv;
812 }
813 
814 
815 		/*
816 		 * shells
817 		 */
818 
819 static int
820 shells(int argc, char *argv[])
821 {
822 	const char	*sh;
823 	int		i, rv;
824 
825 	assert(argc > 1);
826 	assert(argv != NULL);
827 
828 #define SHELLSPRINT	(void)printf("%s\n", sh)
829 
830 	setusershell();
831 	rv = RV_OK;
832 	if (argc == 2) {
833 		while ((sh = getusershell()) != NULL)
834 			SHELLSPRINT;
835 	} else {
836 		for (i = 2; i < argc; i++) {
837 			setusershell();
838 			while ((sh = getusershell()) != NULL) {
839 				if (strcmp(sh, argv[i]) == 0) {
840 					SHELLSPRINT;
841 					break;
842 				}
843 			}
844 			if (sh == NULL) {
845 				rv = RV_NOTFOUND;
846 				break;
847 			}
848 		}
849 	}
850 	endusershell();
851 	return rv;
852 }
853