Home | History | Annotate | Line # | Download | only in getent
      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